Oracle の SPARC M7 プロセッサは、ソフトウェアを高速かつ確実に実行できるようにする Software in Silicon を提供します。Software in Silicon 機能の 1 つが、Silicon Secured Memory (SSM) であり、以前はアプリケーションデータ整合性 (ADI) と呼ばれ、その回路は実行時のデータの破損を引き起こす可能性のある一般的なメモリーアクセスエラーを検出します。
これらのエラーは、誤ったコードまたはサーバーのメモリーへの悪意のある攻撃によって発生することがあります。たとえば、バッファーオーバーフローはセキュリティー上の弱点の主な原因になることがわかっています。さらに、インメモリーデータベースは、重要なデータをメモリー内に保持するため、そのようなエラーがアプリケーションに与える影響が大きくなります。
Silicon Secured Memory は、アプリケーションのメモリーポインタとそれらが指すメモリーにバージョン番号を追加して、最適化された本番コードでのメモリー破損を防ぎます。ポインタバージョン番号が内容のバージョン番号と一致しない場合、メモリーアクセスは中止されます。Silicon Secured Memory は、ソフトウェアエラーによって発生するメモリー破損に対して脆弱な C や C++ などのシステムレベルのプログラミング言語で書かれたアプリケーションで機能します。
Oracle Developer Studio 12.5 には、libdiscoverADI.so ライブラリ (discover ADI ライブラリとも呼ばれる) が含まれ、これは隣接するデータ構造に確実に異なるバージョン番号が指定される更新済みの malloc() ライブラリルーチンを提供します。これらのバージョン番号により、プロセッサの SSM テクノロジが、バッファーオーバーフローのようなメモリーアクセスエラーを検出できます。古いポインタアクセスを防ぐため、メモリー内容のバージョン番号はメモリー構造が解放されるときに変更されます。discover および libdiscoverADI.so によって捕捉されるエラーの詳細については、libdiscoverADI ライブラリによって捕捉されるエラーを参照してください。
本番環境で SSM を使用して潜在的なメモリー破損問題を検出することに加えて、アプリケーション開発時にそれを使用して、アプリケーションのテストと動作保証時にそのようなエラーが捕捉されるようにすることもできます。アプリケーションは破損の発生後かなりたってから破損したデータを検出するため、メモリー破損のバグの発見はきわめて困難です。Oracle Developer Studio 開発者ツールスイートの一部である discover ツールと libdiscoverADI.so ライブラリは、誤ったコードの特定と修正を容易にする追加のアプリケーション情報を提供します。
discover ADI ライブラリの libdiscoverADI.so は、無効なメモリーアクセスを引き起こすプログラミングエラーを報告します。次の 2 つの方法で使用できます。
LD_PRELOAD_64 環境変数でアプリケーションに discover ADI ライブラリをプリロードすることによって。この方法では、アプリケーションのすべての 64 ビットバイナリを ADI モードで実行します。たとえば、server というアプリケーションを通常どおりに実行した場合、コマンドは次のようになります。
$ LD_PRELOAD_64=install-dir/lib/compilers/sparcv9/libdiscoverADI.so server
特定のバイナリに対して、–i adi オプションを付けた discover コマンドで ADI モードを使用することによって。
% discover -i adi a.out % a.out
エラーはデフォルトで a.out.html ファイルに報告されます。discover レポートの詳細については、discover レポートの分析および出力オプションを参照してください。
libdiscoverADI 使用の要件と制限を参照してください。
libdiscoverADI.so ライブラリは次のエラーを捕捉します。
配列範囲外アクセス (Array out of Bounds Access) (ABR/ABW)
解放済みメモリーへのアクセス (Freed Memory Access) (FMR/FMW)
古いポインタアクセス (Stale Pointer Access) (特殊なタイプの FMR/FMW)
非割り当て読み取り/書き込み (Unallocated Read/Write) (UAR/UAW)
メモリーの二重解放 (Double Free Memory) (DFM)
これらの各エラーのタイプの詳細については、メモリーアクセスエラーと警告を参照してください。
完全な例については、discover ADI モードの使用例を参照してください。
次のオプションは、ADI での計測時に discover レポートで生成される情報の精度と量を指定します。
このフラグを on に設定すると、discover ADI ライブラリはエラーの場所とエラースタックトレースを報告します。この情報は、エラーを捕捉するには十分ですが、エラーを修正するために必ずしも十分なわけではありません。このオプションの実行時動作を変更する方法については、SUNW_DISCOVER_OPTIONS 環境変数を参照してください。
このフラグは、不正なメモリー領域が割り当てられ、解放された場所に関する情報も生成します。たとえば、エラーがArray out of Bounds Access であったことと、その配列が割り当てられた場所が、出力に示されていることがあります。off に設定されている場合、割り当てとスタックトレースは報告されません。デフォルトは on です。
バッファーオーバーフローアクセスが、バッファーの末尾のあと、またはバッファーの先頭の前の大きなオフセットで発生している場合。
libdiscoverADI.so がリソース制限に達した場合。この場合、discover は、エラーがバッファーオーバーフローであるかどうかを判断するために必要な割り当てスタックトレースを維持できる可能性があります。
このフラグを off に設定すると、ADI は正確でないモードで実行されます。正確でないモードでは、正確な命令が実行されてからいくつかの命令 (ソース行) が実行されたあとに、メモリー書き込みエラーが捕捉されます。正確なモードを有効にするには、このフラグを on (デフォルト) に設定します。
実行時のパフォーマンスを向上させるために、–A off、–P off を指定でき、または両方のオプションを off に設定できます。
エンタープライズアプリケーションは、多くの場合それ自体のメモリーを管理でき、システム (libc) の malloc(3C) ライブラリを使用しません。このような場合、malloc() に割り込む libdiscoverADI.so の通常の使用では、メモリー破損を捕捉できません。メモリー領域が割り当てられたり解放されたりしたときにエンタープライズアプリケーションが libdiscoverADI.so に伝えることができるように、Oracle Developer Studio は API を提供します。libdiscoverADI.so は、SSM のバージョン管理、シグナル処理、およびエラー報告を管理します。これらの API と libdiscoverADI.so は、mmap(2) または shmget(2) によって割り当てられたメモリーでのみ機能します。
次の API が用意されています。
アロケータは、渡されようとしているポインタをメモリーに渡して、メモリーを要求しているクライアントにサイズを渡します。メモリーで ADI が有効になっていない場合、ライブラリは memcntl(2) 呼び出しを使用します。バージョン管理されたポインタが返され、アロケータはこれをクライアントに渡す必要があります。
アロケータは、解放されるメモリー領域にポインタとサイズを渡します。
アロケータは、ポインタ演算を使用して、クライアントメモリーのメタデータにアクセスします。クライアントメモリーは多くの場合バージョン管理されますが、アロケータメタデータはバージョン管理されません。そのような状況では、この API を使用する必要があります。アロケータは任意のポインタを渡し、バージョン管理されていない同等のポインタがアロケータに返されます。
アロケータが、バージョン管理されたポインタを見失うことがあります。この API は任意のポインタを取り込み、正しいバージョンの同等のポインタを返します。戻り値を使用した間接参照によって ADI SEGV は発生しません。
メモリー領域が別の目的で再利用されている場合は、ADI のバージョンをクリアする必要があります。たとえば、メモリー領域がアロケータクライアントによる使用のためにバージョン管理されていて、現在はメタデータのためにアロケータによって使用されている場合です。この関数は、バージョン管理されたポインタまたはバージョン管理されていないポインタおよびサイズを取り込んで、その領域のバージョンを 0 に設定し、バージョン管理されていないポインタを返します。
アプリケーションは独自のメモリー割り当ておよび解放リストを管理できます (たとえば、プログラムで大きなチャンクのメモリーを割り当てたり、それを分割したりすることによって)。ADI バージョン管理 API を使用して管理対象メモリーのエラーを捕捉する方法については、アプリケーションデータ整合性と Oracle Solaris Studio を使用したメモリーアクセスエラーの検出と修正に関するドキュメントを参照してください。
これらの API を使用するには、次のヘッダーファイルをソースに組み込みます。
install-dir/lib/compilers/include/cc/discoverADI_API.h
その後、次のいずれかを行います。
ソースを install-dir/lib/compilers/sparcV9/libadiplugin.so とリンクします
環境変数 LD_PRELOAD_64 を install-dir/lib/compilers/sparcV9/libadiplugin.so に設定します
discover -i adi executable コマンドを実行します
次に、カスタムメモリーアロケータの使用例を示します。
使用例 1 カスタムメモリーアロケータの使用次に、ヘッダーファイルの例を示します。
% cat allocator.h #define GRANULARITY 32 void *mymalloc(size_t len); void myfree(void *ptr);
次に、カスタムメモリーアロケータと libdiscoverADI ライブラリを使用するソースコードの例を示します。
% cat allocator.c #include <sys/mman.h> #include <stdio.h> #include <stdlib.h> #include <ucontext.h> #include <errno.h> #include <dlfcn.h> #include <unistd.h> #include <assert.h> #include "allocator.h" #include "discoverADI_API.h" #pragma init (setup_allocator) #pragma fini (takedown_allocator) #define MAX_ALLOCATIONS 1024 #define START_ADDRESS 0x200000000 uint64_t next_available_address = 0; size_t allocation_table[MAX_ALLOCATIONS/GRANULARITY]; static void setup_allocator() { // mmap with a specific address void *addr = mmap((void *) START_ADDRESS, MAX_ALLOCATIONS, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); if (addr == MAP_FAILED) { fprintf(stderr, "mmap failed {%d}\n", errno); exit(1); } next_available_address = (uint64_t) addr; } static void takedown_allocator() { if (munmap((void *) START_ADDRESS, MAX_ALLOCATIONS) != 0) { fprintf(stderr, "munmap failed {%d}\n", errno); exit(1); } } // Simple malloc void *mymalloc(size_t size) { void *vaddr = (void *) next_available_address; next_available_address += size; assert(next_available_address < (START_ADDRESS+MAX_ALLOCATIONS)); int index = ((uint64_t)vaddr-START_ADDRESS)/GRANULARITY; allocation_table[index] = size; // Tell libdiscoverADI.so about the allocation, get a versioned // pointer vaddr = adi_mark_malloc(vaddr, size); // Return the versioned pointer return vaddr; } // Simple free void myfree(void *ptr) { int index = ((uint64_t)ptr-START_ADDRESS)/GRANULARITY; size_t size = allocation_table[index]; // Tell libdiscoverADI.so about the free(). adi_mark_free(ptr, size); }
% cat simple.c #include <stdio.h> #include <stdlib.h> #include "allocator.h" int main() { // Allocate char *buf1 = (char *) mymalloc(GRANULARITY*2); buf1[0] = 'x'; // Read before free printf("Read buf1[0] before free: [0x%p] %c\n", buf1, buf1[0]); // Allocate char *buf2 = (char *) mymalloc(GRANULARITY*2); // Buf1fer overflow buf1 printf("Read buf1[%d] past end: [0x%p] %c\n", GRANULARITY*2, buf1+GRANULARITY*2, buf1[GRANULARITY*2]); // Free myfree(buf1); // Read after free printf("Read buf1[0] after free: [0x%p] %c\n", buf1, buf1[0]); return 0; }
この例をコンパイルして実行するには:
% cc –m64 –g –I install-dir/lib/compilers/include/cc allocator.c simple.c install-dir/lib/compilers/sparcv9/libadiplugin.so –o simple % ./simple
次に、結果として生成された HTML レポートを示します。
discover の ADI モードは、Oracle Solaris 11.2.8 または Oracle Solaris 11.3 以上を実行する SPARC M7 チップ上の 64 ビットアプリケーションでのみ使用できます。
メモリー検査のための計測と同様に、libdiscoverADI.so の関数が同じ割り当て関数に割り込んだ場合、プリロードされたライブラリが競合することがあります。詳細については、プリロードまたは監査を使用するバイナリは互換性がないを参照してください。
libdiscoverADI でコードを検査する場合のその他の制限には次のものが含まれます。
ヒープ検査のみ使用できます。スタック検査、静的配列範囲外検査、リーク検出はありません。
メタデータを格納するために 64 ビットアドレスの未使用のビットを使用するアプリケーションでは動作しません。一部の 64 ビットアプリケーションでは、ロックなど、メタデータを格納するために 64 ビットアドレスの現在未使用の上位ビットを使用することがあります。そのようなアプリケーションでは、discover の ADI モードは機能しません。この機能は、バージョン情報を格納するために、64 ビットアドレスの上位 4 ビットを使用して動作するためです。
たとえば、2 つの連続した割り当ての間の距離など、ヒープアドレスに関する仮定のもとにポインタ演算を行うアプリケーションでは機能しないことがあります。
memcheck モード (–i memcheck で計測) と異なり、ADI モードでは、アプリケーションが実行可能ファイル内で標準メモリー割り当て関数を再定義している場合に、エラーを捕捉しません。アプリケーションがライブラリ内で標準メモリー割り当て関数を再定義している場合は、ADI モードが機能します。
バッファーオーバーフローの解像度は 64 バイトです。64 バイトで整列されている割り当てでは、libdiscoverADI.so は 1 バイト以上の単位でオーバーフローを捕捉します。64 バイトで整列されない割り当てでは、数バイト単位でバッファーオーバーフローを見落とす可能性があります。一般に、1 から 63 バイト単位のオーバーフローは、割り当ての整列と libdiscoverADI.so がキャッシュ行内に割り当てを配置する場所によって捕捉されないことがあります。
–xipo=2 でコンパイルされたバイナリには、擬陽性 SSM エラーにつながり、結果としてトラップ処理のためにパフォーマンスの低下をまねくような方法でアドレスを操作するメモリー最適化コードが含まれる可能性がわずかにあります。
このセクションでは、ADI モードを使用した discover によって捕捉され、報告される配列範囲外エラーのあるコードサンプルについて説明します。
次のサンプルコードが testcode.c という名前のファイルにあるものと想定します。
#include <stdio.h> #include <stdlib.h> int main() { char *arr1 = (char*)malloc(sizeof(char)*STRSZ); char *arr2 = (char*)malloc(sizeof(char)*STRSZ); // Buffer overflow due to using "<=" instead of "<" for (int i=0; i <= STRSZ; i++) arr1[i] = arr2[i]; // ABR/ABW free(arr1); free(arr2); char *arr3 = (char*)malloc(sizeof(char)*STRSZ); arr3[0] = arr2[0]; // FMR arr2[0] = arr3[3]; // FMWarr3[1] = arr1[1]; // Possible stale pointer/FMR free(arr2); // Double Free return 0; }
次のコマンドを使用して、テストコードを構築します。
$ cc testcode.c -g -m64
このサンプルアプリケーションを ADI モードで実行するには、次のコマンドを使用します。
$ discover -w - -i adi -o a.out.adi a.out $ ./a.out.adi
このコマンドは、discover レポートに次の出力を生成します。これらのレポートの見方と内容の詳細については、discover レポートの分析を参照してください。
ERROR 1 (ABR): reading memory beyond array bounds at address 0x3fffffff7d47e080 {memory: v8}: main() + 0x48 <a.c:13> 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) 13:=> arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16: free(arr2); was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) ERROR 2 (ABW): writing to memory beyond array bounds at address 0x2fffffff7d47e040 {memory: v3}: main() + 0x54 <a.c:13> 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) 13:=> arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16: free(arr2); was allocated at (0x2fffffff7d47e000, 64 bytes): main() + 0x8 <a.c:8> 5: int main() 6: { 7: 8:=> char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9: char *arr2 =(char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" ERROR 3 (FMR): reading from freed memory at address 0x3fffffff7d47e040 {memory: v10}: main() + 0xa0 <a.c:20> 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: 20:=> arr3[0] = arr2[0]; // FMR 21: arr2[0] = arr3[3]; // FMW 22: arr3[1] = arr1[1]; // Possible stale pointer/FMR 23: was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) freed at (0x3fffffff7d47e040, 64 bytes): main() + 0x80 <a.c:16> 13: arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16:=> free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: ERROR 4 (FMW): writing to freed memory at address 0x3fffffff7d47e040 {memory: v10}: main() + 0xb8 <a.c:21> 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: 20: arr3[0] = arr2[0]; // FMR 21:=> arr2[0] = arr3[3]; // FMW 22: arr3[1] = arr1[1]; // Possible stalepointer/FMR 23: 24: free(arr2); // Double Free was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 =(char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) freed at (0x3fffffff7d47e040, 64 bytes): main() + 0x80 <a.c:16> 13: arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16:=> free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: ERROR 5 (FMR): reading from freed memory at address 0x2fffffff7d47e001 {memory: v3}: main() + 0xc0 <a.c:22> 19: 20: arr3[0] = arr2[0]; // FMR 21: arr2[0] = arr3[3]; // FMW 22:=> arr3[1] = arr1[1]; // Possible stale pointer/FMR 23: 24: free(arr2); // Double Free 25: was allocated at (0x2fffffff7d47e000, 64 bytes): main() + 0x8 <a.c:8> 5: int main() 6: { 7: 8:=> char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9: char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" freed at (0x2fffffff7d47e000, 64 bytes): main() + 0x74 <a.c:15> 12: for (int i=0; i <= STRSZ; i++) 13: arr1[i] = arr2[i]; //ABR/ABW 14: 15:=> free(arr1); 16: free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); ERROR 6 (DFM): double freeing memory at address 0x3fffffff7d47e040 {memory: v10}: main() + 0xd0 <a.c:24> 21: arr2[0] = arr3[3]; // FMW 22: arr3[1] = arr1[1]; // Possible stale pointer/FMR 23: 24:=> free(arr2); // Double Free 25: 26: return 0; 27: } was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) freed at (0x3fffffff7d47e040, 64 bytes): main() + 0x80 <a.c:16> 13: arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16:=> free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: DISCOVER SUMMARY: unique errors : 6 (6 total)