メインコンテンツ

CWE Rule 119

Improper Restriction of Operations within the Bounds of a Memory Buffer

R2023a 以降

説明

ルールの説明

The software performs operations on a memory buffer, but it can read from or write to a memory location that is outside of the intended boundary of the buffer.

Polyspace 実装

ルール チェッカーは以下の問題をチェックします。

  • 配列が範囲外にアクセス

  • 汚染されたインデックスによる配列へのアクセス

  • 不正確な文字列形式指定子によるバッファー オーバーフロー

  • 異なる型のオブジェクトを指すポインターへのキャスト

  • 文字列操作で格納先バッファーがオーバーフローしています

  • 文字列操作で格納先バッファーがアンダーフローしています

  • 標準ライブラリ メモリ ルーチンの無効な使用

  • 標準ライブラリ ルーチンの無効な使用

  • 標準ライブラリ文字列ルーチンの無効な使用

  • データの長さとサイズが一致していません

  • string 配列での null 値の欠落

  • 範囲外にアクセスするポインター

  • 汚染されたオフセットによるポインターのデリファレンス

  • sizeof を誤って使用している可能性があります

  • 最初に再初期化せずに、再割り当てされたメモリを別の型のオブジェクトから読み取る

  • 汚染された NULL 文字列または非 NULL 終端文字列

  • 危険な標準関数を使用しています

  • 不確定文字列の使用

すべて展開する

問題

この問題は、配列へのアクセス中に配列のインデックスが範囲 [0...array_size-1] を外れた場合に発生します。

リスク

配列の範囲外へのアクセスは未定義の動作です。予測不能な値を読み取ったり、許可されていない位置へのアクセスを試みてセグメンテーション違反が発生したりする可能性があります。

修正方法

修正方法は欠陥の根本原因によって異なります。たとえば、ループ内の配列にアクセスする際、次のいずれかの状況が発生したとします。

  • ループの上限が大きすぎる。

  • ループ インデックスより 1 小さい配列インデックスを使用せずに、ループ インデックスと同じ配列インデックスを使用している。

この問題を修正するには、ループの範囲または配列インデックスを変更する必要があります。

配列インデックスが配列範囲を超える別の理由として、事前に行われた符号付き整数から符号なし整数への変換が考えられます。この変換はインデックス値のラップ アラウンドを発生させる可能性があり、最終的に配列インデックスが配列範囲を超える原因になります。

多くの場合、結果の詳細 (または Polyspace as You Code のソース コード ツールヒント) には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して、欠陥に関連する変数のこれまでの参照を検索し、関連するイベントを検出できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈またはPolyspace Access Web インターフェイスでの Bug Finder の結果の解釈 (Polyspace Access)も参照してください。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

チェッカーの拡張

入力値が不明であり、入力のサブセットのみがエラーの原因となっている場合、既定の Bug Finder 解析ではこの欠陥が報告されない可能性があります。特定のシステム入力値を原因とする欠陥の有無をチェックするには、より厳密な Bug Finder 解析を実行してください。特定のシステム入力値から欠陥を見つけるための Bug Finder チェッカーの拡張を参照してください。

例 — 配列が範囲外にアクセス エラー
#include <stdio.h>

void fibonacci(void)
{
    int i;
    int fib[10];
 
    for (i = 0; i < 10; i++) 
       {
        if (i < 2) 
            fib[i] = 1;
         else 
            fib[i] = fib[i-1] + fib[i-2];
       }

    printf("The 10-th Fibonacci number is %i .\n", fib[i]);  //Noncompliant
    /* Defect: Value of i is greater than allowed value of 9 */
}

配列 fib にはサイズ 10 が割り当てられています。fib の配列インデックスに許可される値は [0,1,2,...,9] です。変数 ifor ループから出るときの値は 10 です。したがって、printf ステートメントは i を用いて fib[10] にアクセスしようと試みます。

修正 — 配列のインデックスを配列の範囲内に維持

1 つの修正方法として、for ループの後で、fib[i] ではなく fib[i-1] を出力するとします。

#include <stdio.h>

void fibonacci(void)
{
   int i;
   int fib[10];

   for (i = 0; i < 10; i++) 
    {
        if (i < 2) 
            fib[i] = 1;
        else 
            fib[i] = fib[i-1] + fib[i-2];
    }

    /* Fix: Print fib[9] instead of fib[10] */
    printf("The 10-th Fibonacci number is %i .\n", fib[i-1]); 
}

printf ステートメントは、fib[10] ではなく fib[9] にアクセスします。

問題

この問題は、セキュリティで保護されていないソースから取得され、検証されていないインデックスを使用して配列にアクセスした場合に発生します。

リスク

インデックスは有効な配列範囲を外れている可能性があります。汚染されたインデックスが配列範囲を外れていると、以下の原因となることがあります。

  • バッファー アンダーフローまたはアンダーライト — バッファーの先頭より前のメモリへの書き込み。

  • バッファー オーバーフロー — バッファーの末尾より後のメモリへの書き込み。

  • バッファーのオーバーリード — 対象バッファーの末尾より後のメモリへのアクセス。

  • バッファーのアンダーリード、すなわち、対象バッファーの先頭より前のメモリへのアクセス。

攻撃者は無効な読み取りや書き込みの操作を利用して、プログラムに問題を生じさせることができます。

修正方法

インデックスを使用して配列にアクセスする前に、インデックス値を検証して、それが配列範囲内にあることを確認します。

チェッカーの拡張

既定では、Polyspace® は外部ソースからのデータは汚染されていると仮定します。Polyspace 解析での汚染のソースを参照してください。Polyspace 解析の現在のスコープ以外から発生したすべてのデータを汚染されたものと見なすには、コマンド ライン オプション [-consider-analysis-perimeter-as-trust-boundary] を使用します。

例 — インデックスを使用してバッファーの値を返す
#include <stdlib.h>
#include <stdio.h>
#define SIZE100 100
extern int tab[SIZE100];
static int tainted_int_source(void) {
  return strtol(getenv("INDEX"),NULL,10);
}
int taintedarrayindex(void) {
	int num = tainted_int_source();
    return tab[num];//Noncompliant   //Noncompliant
}

この例では、インデックス num により配列 tab にアクセスします。インデックス num は保護されないソースから取得され、関数 taintedarrayindexnumtab の範囲内にあるかどうかをチェックしません。

修正 — 使用前に範囲をチェック

1 つの修正方法として、num が範囲内にあることを使用前にチェックします。

#include <stdlib.h>
#include <stdio.h>
#define SIZE100 100
extern int tab[SIZE100];
static int tainted_int_source(void) {
	return strtol(getenv("INDEX"),NULL,10);
}
int taintedarrayindex(void) {
	int num = tainted_int_source();
	if (num >= 0 && num < SIZE100) {
		return tab[num]; 
	} else {
		return -1;
	}
}

問題

この問題は、sscanf などの関数の書式指定子引数が、メモリ バッファー引数でのオーバーフローまたはアンダーフローにつながる場合に発生します。

リスク

形式指定子で指定した精度がメモリ バッファー サイズより大きいと、オーバーフローが発生します。オーバーフローは、メモリ破損のような予期しない動作を引き起こす可能性があります。

修正方法

メモリ バッファー サイズに適合する形式指定子を使用します。

例 — メモリ バッファー オーバーフロー
#include <stdio.h>

void func (char *str[]) {
    char buf[32];
    sscanf(str[1], "%33c", buf); //Noncompliant
}

この例では、bufchar 要素を 32 個格納できます。したがって、形式指定子 %33c はバッファー オーバーフローの原因となります。

修正 — より低い精度を形式指定子に使用

考えられる修正の 1 つは、バッファーに読み込む要素の数を減らすことです。

#include <stdio.h>

void func (char *str[]) {
    char buf[32];
    sscanf(str[1], "%32c", buf);
}
問題

この問題は、あるオブジェクト型へのポインターと別のオブジェクト型へのポインターの間でキャストを実行する場合に発生します。

リスク

オブジェクトへのポインターが別のオブジェクトへのポインターにキャストされる場合、結果のポインターは正しく配置されない可能性があります。不適切なアライメントは未定義の動作を発生させます。

変換によって正しく配置されたポインターが生成されたとしても、そのポインターがオブジェクトへのアクセスに使用される場合、動作が未定義となることがあります。

例外:オブジェクト型を指すポインターは、以下のいずれかの型を指すポインターに変換できます。

  • char

  • signed char

  • unsigned char

例 - 範囲のより広いオブジェクト型を指すポインターへのキャスト
signed   char *p1;
unsigned int *p2;

void foo(void){ 
  p2 = ( unsigned int * ) p1;     /* Non-compliant */				
}

この例では、p1signed char オブジェクトを指します。しかしながら、p1 は範囲のより広いオブジェクト型 unsigned int を指すポインターにキャストされています。

例 - 範囲のより狭いオブジェクト型を指すポインターへのキャスト
extern unsigned int read_value ( void );
extern void display ( unsigned int n );

void foo ( void ){
  unsigned int u = read_value ( );
  unsigned short *hi_p = ( unsigned short * ) &u;    /* Non-compliant  */	
  *hi_p = 0;                                         
  display ( u );                                     
}

この例では、uunsigned int 変数です。&u は、範囲のより狭いオブジェクト型 unsigned short を指すポインターにキャストされています。

ビッグエンディアン マシンでは、ステートメント *hi_p = 0&u が指すメモリ位置の高位ビットをクリアしようと試みます。しかし、display(u) の結果を見ると、その高位ビットがクリアされていないことがあります。

例 - 型修飾子を追加するキャスト
const short *p;
const volatile short *q;
void foo (void){
  q = ( const volatile short * ) p;  /* Compliant */								
}

この例では、pq のいずれも short オブジェクトを指します。この両者間のキャストは volatile 修飾子のみを追加し、したがって準拠性をもちます。

問題

この問題は、特定の文字列操作関数によって、その格納先バッファー引数にバッファー サイズを超えるオフセットで書き込まれた場合に発生します。

たとえば、関数 sprintf(char* buffer, const char* format) を呼び出す際に、buffer より大きいサイズの定数文字列 format を使用する場合などです。

リスク

バッファー オーバーフローにより、メモリ破損やシステム停止といった予期しない動作を引き起こす可能性があります。また、バッファー オーバーフローは、コード インジェクションのリスクにもつながります。

修正方法

1 つの解決策として、代替となる関数を使用して、書き込まれる文字の数を制限します。次に例を示します。

  • 書式設定されたデータを文字列に書き込むのに sprintf を使用している場合は、代わりに snprintf_snprintf または sprintf_s を使用して長さを制御します。あるいは、asprintf を使用して、格納先バッファーに必要なメモリを自動で割り当てます。

  • 書式設定されたデータを可変引数リストから文字列に書き込むのに vsprintf を使用している場合は、代わりに vsnprintf または vsprintf_s を使用して長さを制御します。

  • ワイド文字列をコピーするのに wcscpy を使用している場合は、代わりに wcsncpywcslcpy または wcscpy_s を使用して長さを制御します。

別の解決策として、バッファー サイズを増やします。

例 — sprintf の使用におけるバッファー オーバーフロー
#include <stdio.h>

void func(void) {
    char buffer[20];
    char *fmt_string = "This is a very long string, it does not fit in the buffer";

    sprintf(buffer, fmt_string); //Noncompliant
}

この例では、bufferchar 要素を 20 個格納できますが、fmt_string はより大きいサイズとなっています。

修正 — snprintfsprintf の代わりに使用

1 つの修正方法として、関数 snprintf を使用して長さを制御します。

#include <stdio.h>

void func(void) {
    char buffer[20];
    char *fmt_string = "This is a very long string, it does not fit in the buffer";

    snprintf(buffer, 20, fmt_string);
}
問題

この問題は、特定の文字列操作関数によって、その格納先バッファー引数にバッファーの先頭から負のオフセットで書き込まれた場合に発生します。

たとえば、関数 sprintf(char* buffer, const char* format) では buffer を演算 buffer = (char*)arr; ... buffer += offset; から取得します。arr は配列で、offset は負の値です。

リスク

バッファー アンダーフローにより、メモリ破損やシステム停止のような予期しない動作を引き起こす可能性があります。また、バッファー アンダーフローは、コード インジェクションのリスクにもつながります。

修正方法

格納先バッファーの引数がポインター演算の結果得られたものである場合は、ポインターをデクリメントしているかどうかを確認します。デクリメント前の元の値かデクリメント値を変更することで、ポインターのデクリメントを修正します。

例 — sprintf の使用におけるバッファー アンダーフロー
#include <stdio.h>
#define offset -2

void func(void) {
    char buffer[20];
    char *fmt_string ="Text";

    sprintf(&buffer[offset], fmt_string); //Noncompliant
}

この例では、&buffer[offset]buffer に割り当てられているメモリから負のオフセットの位置にあります。

変更 — ポインター デクリメンターを変更

1 つの修正方法として、offset の値を変更します。

#include <stdio.h>
#define offset 2

void func(void) {
    char buffer[20];
    char *fmt_string ="Text";

    sprintf(&buffer[offset], fmt_string);     
}
問題

この問題は、メモリ ライブラリ関数が無効な引数で呼び出された場合に発生します。たとえば、関数 memcpy でコピー先の配列に格納できないバイト数をコピーする場合が該当します。

リスク

無効な引数でメモリ ライブラリ関数を使用すると、バッファー オーバーフローなどの問題が発生する可能性があります。

修正方法

修正方法は欠陥の根本原因によって異なります。多くの場合、結果の詳細 (または Polyspace as You Code のソース コード ツールヒント) には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して、欠陥に関連する変数のこれまでの参照を検索し、関連するイベントを検出できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈またはPolyspace Access Web インターフェイスでの Bug Finder の結果の解釈 (Polyspace Access)も参照してください。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — 標準ライブラリ メモリ ルーチンの無効な使用エラー
#include <string.h>
#include <stdio.h>

char* Copy_First_Six_Letters(void)
 {
  char str1[10],str2[5];

  printf("Enter string:\n");
  scanf("%9s",str1);

  memcpy(str2,str1,6);  //Noncompliant
  /* Defect: Arguments of memcpy invalid: str2 has size < 6 */

  return str2;
 }

文字列 str2 のサイズは 5 ですが、関数 memcpy を使用して 6 文字の文字列 str1str2 にコピーされています。

修正 — 有効な引数による関数の呼び出し

1 つの修正方法として、関数 memcpy でコピーされる文字が収まるように、str2 のサイズを調整するとします。

#include <string.h>
#include <stdio.h>

char* Copy_First_Six_Letters(void)
 {
  /* Fix: Declare str2 with size 6 */
  char str1[10],str2[6]; 

  printf("Enter string:\n");
  scanf("%9s",str1);

  memcpy(str2,str1,6);
  return str2;
 }
問題

この問題は、文字列ライブラリ関数が無効な引数で呼び出された場合に発生します。

リスク

リスクは無効な引数のタイプによって異なります。たとえば、コピー先引数より大きいコピー元引数を指定して関数 strcpy を使用すると、バッファー オーバーフローが発生する可能性があります。

修正方法

修正方法は欠陥に関連する標準ライブラリ関数に依存します。場合によっては、関数呼び出しの前に関数の引数を制約することができます。たとえば、次の関数 strcpy を考えます。

char * strcpy(char * destination, const char* source)
これが、使用可能なバッファーに比べて大きすぎるバイト数をコピー先引数にコピーしようとする場合、strcpy を呼び出す前にコピー元引数を制約します。場合によっては、代替となる関数を使用してエラーを回避できます。たとえば、strcpy の代わりに strncpy を使用するとコピーされるバイト数を制御できます。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — 標準ライブラリ文字列ルーチンの無効な使用によるエラー
 #include <string.h>
 #include <stdio.h>
 
 char* Copy_String(void)
 {
  char *res;
  char gbuffer[5],text[20]="ABCDEFGHIJKL";

  res=strcpy(gbuffer,text);  //Noncompliant
  /* Error: Size of text is less than gbuffer */

  return(res);
 }

文字列 text はサイズが gbuffer より大きくなっています。したがって、関数 strcpytextgbuffer にコピーできません。

修正 — 有効な引数を使用

1 つの修正方法として、コピー先の文字列 gbuffer をソース文字列 text 以上のサイズで宣言するとします。

#include <string.h>
 #include <stdio.h>
 
 char* Copy_String(void)
 {
  char *res;
  /*Fix: gbuffer has equal or larger size than text */
  char gbuffer[20],text[20]="ABCDEFGHIJKL";

  res=strcpy(gbuffer,text);

  return(res);
 }
問題

この問題は、バッファー オーバーフローから保護するために、memcpymemsetmemmove などのメモリ コピー関数の長さ引数とデータ バッファー引数がチェックされていない場合に発生します。

リスク

データ バッファーや長さの引数を攻撃者が操作できる場合、その攻撃者は、実際のデータ サイズをその長さより小さくすることによってバッファー オーバーフローを引き起こすことができます。

長さのこうした不一致のため、攻撃者は、データ バッファーに収まらないメモリを新しい場所にコピーできます。余剰分のメモリに機密情報が含まれている場合、攻撃者はそのデータにアクセスできるようになります。

この欠陥は、SSL Heartbleed バグと類似しています。

修正方法

メモリのコピーまたは操作を行う際、長さの引数をデータから直接計算して、サイズが一致するようにします。

例 — データのバッファーをコピー
#include <stdlib.h>
#include <string.h>

typedef struct buf_mem_st {
    char *data;
    size_t max;     /* size of buffer */
} BUF_MEM;

extern BUF_MEM beta;

int cpy_data(BUF_MEM *alpha)
{
    BUF_MEM *os = alpha;
    int num, length;

    if (alpha == 0x0) return 0;
    num = 0;

    length = *os->data;
    memcpy(&(beta.data[num]), os->data + 2, length); //Noncompliant

    return(1);
}

この関数では、バッファー alpha をバッファー beta にコピーします。しかし、変数 lengthdata+2 とは関連していません。

修正 — バッファー長をチェック

1 つの修正方法として、バッファーの長さを、最大値から 2 を引いた値に対してチェックします。このチェックにより、構造体 beta にデータをコピーする十分なスペースが確保されます。

#include <stdlib.h>
#include <string.h>

typedef struct buf_mem_st {
    char *data;
    size_t max;     /* size of buffer */
} BUF_MEM;

extern BUF_MEM beta;

int cpy_data(BUF_MEM *alpha)
{
    BUF_MEM *os = alpha;
    int num, length;

    if (alpha == 0x0) return 0;
    num = 0;

    length = *os->data;
    if (length<(os->max -2)) {
        memcpy(&(beta.data[num]), os->data + 2, length); 
    }

    return(1);

}
問題

この問題は、null 文字 '\0' で終了するだけの十分なスペースが文字列にない場合に発生します。

この欠陥は C のプロジェクトのみに当てはまります。

リスク

暗黙的な NULL 終端を仮定せずに文字列を配列にコピーすると、バッファー オーバーフローが発生する可能性があります。

修正方法

文字配列をリテラルで初期化する場合、配列範囲の指定を避けます。

char three[]  = "THREE";
コンパイラが自動的に NULL 終端の領域を割り当てます。前述の例では、コンパイラは 5 つの文字と NULL 終端のために十分な領域を割り当てます。

初期化後に問題が発生した場合、NULL 終端を考慮するために配列のサイズを 1 だけ増やさなければならない場合があります。

特定の状況では、文字列の代わりに文字のシーケンスを使用して文字配列を初期化することが必要な場合があります。この場合、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — 配列のサイズが小さすぎる
void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[5] = "THREE"; //Noncompliant
}

文字配列 three のサイズは 5 で、この配列には 'T''H''R''E' および 'E' の 5 つの文字があります。three の大きさは 5 バイトしかないため、最後に null 文字を入れる余地がありません。

修正 — 配列のサイズを増加

1 つの修正方法として、配列のサイズを変更して 5 つの文字と null 文字が入るようにできます。

void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[6] = "THREE";
}
修正 — 初期化の方法を変更

1 つの修正方法として、配列のサイズを空白にしたままで文字列を初期化することができます。この初期化方法では、5 つの文字と終端の null 文字のために十分なメモリが割り当てられます。

void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[]  = "THREE";
}
問題

この問題は、ポインターがその範囲外でデリファレンスされた場合に発生します。

ポインターにアドレスが割り当てられると、そのポインターにメモリのブロックが関連付けられます。そのポインターを使用してそのブロック外のメモリにアクセスすることはできません。

リスク

範囲外のポインターのデリファレンスは未定義の動作です。予測不能な値を読み取ったり、許可されていない位置へのアクセスを試みてセグメンテーション違反が発生したりする可能性があります。

修正方法

修正方法は欠陥の根本原因によって異なります。たとえば、ループ内のポインターをデリファレンスする際、次のいずれかの状況が発生したとします。

  • ループの上限が大きすぎる。

  • ポインターをインクリメントするためにポインター演算を使用する際、不適切な値でポインターを進めている。

この問題を修正するには、ループの範囲またはポインターのインクリメント値を変更する必要があります。

多くの場合、結果の詳細 (または Polyspace as You Code のソース コード ツールヒント) には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して、欠陥に関連する変数のこれまでの参照を検索し、関連するイベントを検出できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈またはPolyspace Access Web インターフェイスでの Bug Finder の結果の解釈 (Polyspace Access)も参照してください。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — 範囲外にアクセスするポインター エラー
int* Initialize(void)
{
 int arr[10];
 int *ptr=arr;

 for (int i=0; i<=9;i++)
   {
    ptr++;
    *ptr=i;   //Noncompliant
    /* Defect: ptr out of bounds for i=9 */
   }

 return(arr);
}

ptr には、サイズ 10*sizeof(int) のメモリ ブロックを指すアドレス arr が割り当てられます。for ループで、ptr は 10 回インクリメントされます。ループの最後の反復で、ptr は割り当てられたメモリ ブロックの外を指します。したがって、デリファレンスはできません。

修正 — ポインターが範囲内にあることをチェック

1 つの修正方法として、ptr のインクリメントとデリファレンスの順序を逆にすることができます。

int* Initialize(void)
{
 int arr[10];
 int *ptr=arr;

 for (int i=0; i<=9;i++)
     {
      /* Fix: Dereference pointer before increment */
      *ptr=i;
      ptr++;
     }

 return(arr);
}

最後のインクリメントの後で ptr は割り当てられたメモリ ブロックの外を指しますが、もうデリファレンスされることはありません。

問題

この問題は、ポインターのデリファレンスで、不明なソースまたはセキュリティで保護されていないソースからのオフセット変数が使用された場合に発生します。

このチェックでは、動的に割り当てられたバッファーに注目します。静的バッファーのオフセットについては、汚染されたインデックスによる配列へのアクセスを参照してください。

リスク

インデックスは有効な配列範囲を外れている可能性があります。汚染されたインデックスが配列範囲を外れていると、以下の原因となることがあります。

  • バッファー アンダーフローまたはアンダーライト、すなわち、バッファーの先頭より前でのメモリへの書き込み。

  • バッファー オーバーフロー、すなわち、バッファーの末尾より後でのメモリへの書き込み。

  • バッファー オーバーリード、すなわち、対象バッファーの末尾より後でのメモリへのアクセス。

  • バッファーのアンダーリード、すなわち、対象バッファーの先頭より前のメモリへのアクセス。

攻撃者は、無効な読み取りや書き込みを使用してプログラムを侵害できます。

修正方法

変数を使用してポインターにアクセスする前に、インデックスを検証します。変数が有効範囲内にありオーバーフローしないことを確認します。

チェッカーの拡張

既定では、Polyspace は外部ソースからのデータは汚染されていると仮定します。Polyspace 解析での汚染のソースを参照してください。Polyspace 解析の現在のスコープ以外から発生したすべてのデータを汚染されたものと見なすには、コマンド ライン オプション [-consider-analysis-perimeter-as-trust-boundary] を使用します。

例 — ポインター配列のデリファレンス
#include <stdio.h>
#include <stdlib.h>
enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};
extern void read_pint(int*);

int taintedptroffset(void) {
    int offset;
    scanf("%d",&offset);
    int* pint = (int*)calloc(SIZE10, sizeof(int));
    int c = 0;
    if(pint) {
        /* Filling array */
        read_pint(pint);
        c = pint[offset];//Noncompliant
        free(pint);
    }
    return c;
}

この例では、関数が整数ポインター pint を初期化しています。ポインターは、入力インデックス offset を使用してデリファレンスされています。offset の値はポインター範囲外である可能性があり、値域外エラーの原因となります。

修正 — デリファレンス前にインデックスをチェック

1 つの修正方法として、offset の値を検証します。offset が有効範囲内にある場合のみ、ポインターのデリファレンスを続行します。

#include <stdlib.h>
#include <stdio.h>
enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};
extern void read_pint(int*);

int taintedptroffset(void) {
    int offset;
    scanf("%d",&offset);
    int* pint = (int*)calloc(SIZE10, sizeof(int));
    int c = 0;
    if (pint) {
        /* Filling array */
        read_pint(pint);
        if (offset>0 && offset<SIZE10) {
            c = pint[offset];
        }
        free(pint);
    }
    return c;
}
問題

この問題は、Polyspace Bug Finder™sizeof 演算子の使用が原因の、おそらく想定外である結果を検出した場合に発生します。次に例を示します。

  • 配列のサイズを求めるため、sizeof 演算子が配列パラメーター名で使用されている。しかし、配列パラメーター名それ自体がポインターである。sizeof 演算子からは、そのポインターのサイズが返される。

  • 配列のサイズを求めるため、sizeof 演算子が配列要素で使用されている。しかし、演算子はその配列要素のサイズを返す。

  • 正しくない想定のもとに sizeof 演算子を先に使用したため、特定の関数 (strncmpwcsncpy など) の size 引数が不適切になっている。次に例を示します。

    • 関数呼び出し strncmp(string1, string2, num) で、num が、ポインターでの sizeof 演算子の誤使用により取得されている。

    • 関数呼び出し wcsncpy(destination, source, num) で、num がワイド文字の数ではなく、sizeof 演算子を使用して取得されたバイト単位のサイズとなっている。たとえば、wcsncpy(destination, source, (sizeof(desintation)/sizeof(wchar_t)) - 1) ではなく wcsncpy(destination, source, sizeof(destination) - 1) を使用した場合などです。

リスク

sizeof 演算子の不適切な使用は、次の問題の原因となることがあります。

  • sizeof 演算子により配列サイズが返され、その戻り値がループの制限に使用されることを想定している場合に、ループの実行回数が想定より少なくなる。

  • sizeof 演算子の戻り値がバッファーの割り当てに使用される場合に、バッファー サイズが必要なサイズより小さくなる。バッファーが不十分だと、結果としてバッファー オーバーフローなどの脆弱性などにつながることがあります。

  • sizeof 演算子の戻り値が関数呼び出しで不適切に使用される場合に、関数が想定どおりに動作しない。

修正方法

考えられる修正方法は次のとおりです。

  • sizeof 演算子を、配列サイズを決定するために配列パラメーター名や配列要素で使用しない。

    ベスト プラクティスは、配列サイズを別の関数パラメーターとして渡し、そのパラメーターを関数本体で使用することです。

  • sizeof 演算子を慎重に使用して、strncmpwcsncpy のような関数の数値引数を決定する。たとえば、wcsncpy などのワイド文字列関数で、バイト数ではなくワイド文字の数を引数として使用します。

例 — 配列サイズを決定する際の sizeof の誤使用
#define MAX_SIZE 1024

void func(int a[MAX_SIZE]) {
    int i;

    for (i = 0; i < sizeof(a)/sizeof(int); i++)  //Noncompliant
    {
        a[i] = i + 1;
    }
}

この例では、sizeof(a) は配列サイズではなくポインター a のサイズを返します。

修正 — 配列サイズを別の方法で決定

1 つの修正方法として、別の方法を使用して配列サイズを決定します

#define MAX_SIZE 1024

void func(int a[MAX_SIZE]) {
    int i;

    for (i = 0; i < MAX_SIZE; i++)    {
        a[i] = i + 1;
    }
}
問題

この問題は、以下の操作を順に実行すると発生します。

  1. 元の割り当てとは異なる型のオブジェクトにメモリを再割り当てする。

    たとえばこのコードの抜粋では、本来 struct A* 型のポインターに割り当てられていたメモリが、struct B* 型のポインターに再割り当てされます。

    struct A;
    struct B;
    
    struct A *Aptr = (struct A*) malloc(sizeof(struct A));
    struct B *Bptr = (struct B*) realloc(Aptr, sizeof(struct B));

  2. 再割り当てされたメモリを最初に再初期化せずに、このメモリから読み取る。

    再割り当てされたメモリを指すポインターでの読み取りアクセスは、ポインター デリファレンスまたは配列のインデックス付けにおいて発生する可能性があります。const 修飾子付きオブジェクトへのポインターを受け取る関数に、対応するパラメーターとしてこのポインターを渡す操作も、読み取りアクセスとしてカウントされます。

リスク

再初期化されていない再割り当てメモリからの読み取りを行うと、未定義の動作につながります。

修正方法

再割り当ての後、最初の読み取りアクセスの前にメモリを再初期化します。

チェッカーでは、再割り当てされたメモリを指すポインターでの書き込みアクセスすべてが (オブジェクトの再初期化が部分的である場合でも)、再初期化の要件を満たしているものと解釈されます。再割り当てされたメモリを指すポインターでの書き込みアクセスは、ポインター デリファレンスと配列のインデックス付けにおいて発生する可能性があります。const 修飾子付きでないオブジェクトへのポインターを受け取る関数に、対応するパラメーターとしてこのポインターを渡す操作も、書き込みアクセスとしてカウントされます。

例 - 非準拠:最初に再初期化を行わずに、再割り当てされたメモリから読み取る
#include<stdlib.h>

struct group {
    char *groupFirst;
    int groupSize;
};

struct groupWithID {
    int groupID;
    char *groupFirst;
    int groupSize;
};

char* readName();
int readSize();

void createGroup(int nextAvailableID) {
    struct group *aGroup;
    struct groupWithID *aGroupWithID;
    
    aGroup = (struct group*) malloc(sizeof(struct group));
    
    if(!aGroup) {
        /*Handle error*/
    }
    
    aGroup->groupFirst = readName();
    aGroup->groupSize  = readSize();
    
    /* Reassign to group with ID */
    aGroupWithID = (struct groupWithID*) realloc(aGroup, sizeof(struct groupWithID));
    if(!aGroupWithID) {
        free(aGroup);
        /*Handle error*/
    }
    
    if(aGroupWithID -> groupSize > 0) { /* Noncompliant */
        /* ... */
    }
    
    /* ...*/
    free(aGroupWithID);
}

この例では、関数 malloc を使用して group* ポインターに割り当てられたメモリを、関数 realloc を使用して、groupWithID* ポインターに再割り当てします。再割り当てされたメモリを再初期化する前に、このメモリへの読み取りアクセスが行われます。

修正 – 再初期化の後、最初の読み取りの前にメモリを再割り当てする

最初の読み取りアクセスの前に、groupWithID* ポインターに割り当てられているメモリを再初期化します。メモリのすべてのビットは、関数 memset を使用して再初期化できます。

#include<stdlib.h>
#include<string.h>

struct group {
    char *groupFirst;
    int groupSize;
};

struct groupWithID {
    int groupID;
    char *groupFirst;
    int groupSize;
};

char* readName();
int readSize();

void createGroup(int nextAvailableID) {
    struct group *aGroup;
    struct groupWithID *aGroupWithID;
    
    aGroup = (struct group*) malloc(sizeof(struct group));
    
    if(!aGroup) {
        /*Handle error*/
    }
    
    aGroup->groupFirst = readName();
    aGroup->groupSize  = readSize();
    
    /* Reassign to group with ID */
    aGroupWithID = (struct groupWithID*) realloc(aGroup, sizeof(struct groupWithID));
    if(!aGroupWithID) {
        free(aGroup);
        /*Handle error*/
    }
    
    memset(aGroupWithID, 0 , sizeof(struct groupWithID));
    /* Reinitialize group */
    if(aGroupWithID -> groupSize > 0) { 
        /* ... */
    }
    
    /* ...*/
    free(aGroupWithID);
}

問題

この問題は、strcpysprintf などの文字列バッファーを暗黙的にデリファレンスする文字列操作ルーチンで、セキュリティで保護されていないソースからの文字列が使用された場合に発生します。

汚染された NULL 文字列または非 NULL 終端文字列では、scanf ファミリの可変個引数関数の呼び出しから返された文字列に関する欠陥が報告されません。同様に、文字列と一緒に %s 指定子を printf ファミリの可変個引数関数に渡した場合も欠陥は報告されません。

リスク

文字列がセキュリティで保護されないソースに由来している場合、攻撃者により文字列が操作されている可能性や、文字列ポインターが異なるメモリ位置に向けられている可能性があります。

文字列が NULL である場合、文字列ルーチンは文字列をデリファレンスできず、プログラムがクラッシュする原因となります。文字列が null で終了しない場合、文字列ルーチンでは文字列がいつ終了するかわからない可能性があります。このエラーは範囲外への書き込みの原因となり、バッファー オーバーフローを引き起こします。

修正方法

文字列は、使用する前に検証します。以下についてチェックします。

  • 文字列が NULL でない。

  • 文字列が null で終了している。

  • 文字列のサイズが、必要なサイズと一致している。

チェッカーの拡張

既定では、Polyspace は外部ソースからのデータは汚染されていると仮定します。Polyspace 解析での汚染のソースを参照してください。Polyspace 解析の現在のスコープ以外から発生したすべてのデータを汚染されたものと見なすには、コマンド ライン オプション [-consider-analysis-perimeter-as-trust-boundary] を使用します。

例 — 文字列を入力から取得
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
void warningMsg(void)
{
	char userstr[MAX];
	read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	strncat(str, userstr, SIZE128-(strlen(str)+1));//Noncompliant
	print_str(str);
}


この例では、文字列 str は引数 userstr と連結しています。userstr の値は不明です。userstr のサイズが使用可能なスペースより大きい場合、この連結はオーバーフローします。

修正 — データを検証

1 つの修正方法として、strncat で使用する前に、userstr のサイズをチェックして、文字列が必ず null で終了するようにします。この例では、補助関数 sansitize_str を使用して文字列を検証しています。欠陥はこの関数に集中しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
int sanitize_str(char* s) {
	int res = 0; 
	if (s && (strlen(s) > 0)) { // Noncompliant-TAINTED_STRING only flagged here
		// - string is not null
		// - string has a positive and limited size
		// - TAINTED_STRING on strlen used as a firewall
		res = 1;
	}
	return res; 
}
void warningMsg(void)
{
	char userstr[MAX];
	read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (sanitize_str(userstr))	
		strncat(str, userstr, SIZE128-(strlen(str)+1));
	print_str(str);
}
修正 — データを検証

別の修正方法として、特定の文字列を含む関数 errorMsg および warningMsg を呼び出します。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define SIZE128 128

extern void print_str(const char*);

void warningMsg(char* userstr)
{
    char str[SIZE128] = "Warning: ";
    strncat(str, userstr, SIZE128-(strlen(str)+1));
    print_str(str);
}

void errorMsg(char* userstr)
{
  char str[SIZE128] = "Error: ";
  strncat(str, userstr, SIZE128-(strlen(str)+1));
  print_str(str);
}

int manageSensorValue(int sensorValue) {
  int ret = sensorValue;
  if ( sensorValue < 0 ) {
    errorMsg("sensor value should be positive");
    exit(1);
  } else if ( sensorValue > 50 ) {
    warningMsg("sensor value greater than 50 (applying threshold)...");
    sensorValue = 50;
  }
  
  return sensorValue;
}
問題

この問題は、コードで、バッファー オーバーフローになる可能性があるやり方でデータをバッファーに書き込む標準関数が使用されている場合に発生します。

次の表に、危険な標準関数、各関数を使用した場合のリスク、および代用する関数を示します。チェッカーは以下にフラグを設定します。

  • 本質的に危険な関数の使用。

  • コンパイル時にデータが書き込まれるバッファーのサイズを決定可能な場合にのみ、危険な可能性のある関数を使用する。チェッカーは、バッファーが動的に割り当てられるこのような関数の使用にフラグを設定しません。

危険な関数リスク レベルより安全な関数
gets本質的に危険 — コンソールからは入力の長さを制御できない。fgets
std::cin::operator>> および std::wcin::operator>>本質的に危険 — コンソールからは入力の長さを制御できない。

入力長を制御するには、cin 呼び出しの前に cin.width を置きます。このメソッドは入力を切り捨てる結果になる場合があります。

バッファー オーバーフローと入力切り捨てが発生しないようにするには、>> 演算子の格納先に std::string オブジェクトを使用します。

strcpy潜在的に危険 — 格納先バッファーのサイズが小さすぎて、ソース バッファーと null 終端が収まらない場合は、バッファー オーバーフローが発生する可能性がある。 関数 strlen() を使用してソース バッファーのサイズを決定し、格納先バッファーがソース バッファーと null 終端を収められるように十分なメモリを割り当てます。strcpy の代わりに、関数 strncpy を使用します。
stpcpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。stpncpy
lstrcpy または StrCpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。StringCbCopyStringCchCopystrncpystrcpy_s または strlcpy
strcat潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。strncatstrlcat または strcat_s
lstrcat または StrCat潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。StringCbCatStringCchCatstrncaystrcat_s または strlcat
wcpcpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。wcpncpy
wcscat潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。wcsncatwcslcat または wcncat_s
wcscpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。wcsncpy
sprintf潜在的に危険 — 出力の長さが不明な長さや値に依存していると、バッファー オーバーフローが発生する可能性がある。snprintf
vsprintf潜在的に危険 — 出力の長さが不明な長さや値に依存していると、バッファー オーバーフローが発生する可能性がある。vsnprintf
リスク

これらの関数はバッファー オーバーフローの原因となることがあり、攻撃者はこれを利用してプログラムに侵入できます。

修正方法

修正方法は欠陥の根本原因によって異なります。上の表に記載されている修正と以下の修正付きのコード例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — sprintf の使用
#include <stdio.h>
#include <string.h>
#include <iostream>

#define BUFF_SIZE 128


int dangerous_func(char *str)
{
    char dst[BUFF_SIZE];
    int r = 0;

    if (sprintf(dst, "%s", str) == 1) //Noncompliant
    {
        r += 1;
        dst[BUFF_SIZE-1] = '\0';
    }
    
    return r;
}

この関数例では、sprintf を使用して文字列 strdst にコピーしています。しかし、str がバッファーより大きいと、sprintf がバッファー オーバーフローの原因となる場合があります。

修正 — バッファー サイズを指定して snprintf を使用

1 つの修正方法として、snprintf を代わりに使用し、バッファー サイズを指定します。

#include <stdio.h>
#include <string.h>
#include <iostream>

#define BUFF_SIZE 128


int dangerous_func(char *str)
{
    char dst[BUFF_SIZE];
    int r = 0;

    if (snprintf(dst, sizeof(dst), "%s", str) == 1)
    {
        r += 1;
        dst[BUFF_SIZE-1] = '\0';
    }
    
    return r;
}
問題

この問題が発生するのは、下記に示すような fgets ファミリ関数を使用した書き込み操作が成功したかどうかをチェックせず、

char * fgets(char* buf, int n, FILE *stream)
書き込まれたバッファーに有効なコンテンツがあるかどうかをチェックしない場合、または失敗時にバッファーをリセットしない場合です。その後、バッファーに有効な内容が含まれていることを前提とする操作を実行します。たとえば、(上記に示すように) 不確定な内容が含まれている可能性があるバッファーが buf である場合、チェッカーは以下の場合に欠陥を報告します。

  • 文字列やワイド文字列を表示または操作する標準関数の引数として buf を渡す。

  • 関数から buf を返す。

  • パラメーターの型として const char * または const wchar_t * を使用する外部関数の引数として buf を渡す。

  • bufbuf[index] または *(buf + offset) として読み取る (ここで index または offset は、バッファーの先頭からの距離を表す数値です)。

リスク

fgets ファミリ関数が失敗した場合、出力バッファーの内容は不確定になります。このようなバッファーの使用には未定義の動作があり、プログラムによる処理の停止、他のセキュリティの脆弱性発生の原因になる可能性があります。

修正方法

fgets ファミリ関数が失敗した場合、関数の出力バッファーを既知の文字列値にリセットします。

例 — 外部関数に渡される fgets() の出力
#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>

#define SIZE20 20

extern void display_text(const char *txt);

void func(void) {
    char buf[SIZE20];
	
	/* Check fgets() error */
    if (fgets (buf, sizeof (buf), stdin) == NULL)
    {
        /* 'buf' may contain an indeterminate string.  */
        ;
    }
	/* 'buf passed to external function */
    display_text(buf);  //Noncompliant
}
        
      

この例では、出力 buf が外部関数 display_text() に渡されますが、fgets() が失敗しても出力値がリセットされません。

修正 — 失敗時に fgets() の出力をリセット

fgets() が失敗した場合、外部関数に渡す前に buf を既知の値にリセットします。

#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>

#define SIZE20 20

extern void display_text(const char *txt);

void func1(void) {
    char buf[SIZE20];
	/* Check fgets() error */
    if (fgets (buf, sizeof (buf), stdin) == NULL)
    {
		/* value of 'buf' reset after fgets() failure. */
        buf[0] = '\0';
    }
	/* 'buf' passed to external function */
    display_text(buf); 
} 

チェック情報

カテゴリ: その他

バージョン履歴

R2023a で導入

すべて展開する