メインコンテンツ

CWE Rule 131

Incorrect Calculation of Buffer Size

R2023a 以降

説明

ルールの説明

The software does not correctly calculate the size to be used when allocating a buffer, which could lead to a buffer overflow.

Polyspace 実装

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

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

  • 汚染されたサイズでのメモリの割り当て

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

  • 汚染された符号変化の変換

  • 可変長配列の汚染されたサイズ

  • sizeof において使用された誤った型

すべて展開する

問題

この問題は、配列へのアクセス中に配列のインデックスが範囲 [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] にアクセスします。

問題

この問題は、callocmalloc などのメモリ割り当て関数で、セキュリティで保護されていないソースからのサイズ引数が使用された場合に発生します。

リスク

メモリ割り当てが制御されていないと、プログラムによってシステム メモリが過剰に要求されることがあります。その結果、メモリ不足の状態やリソースの過剰割り当てによりクラッシュすることがあります。

修正方法

メモリを割り当てる前に引数の値をチェックして、範囲を超えないことを確認します。

チェッカーの拡張

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

例 — ユーザーからの入力を使用したメモリの割り当て
#include<stdio.h>
#include <stdlib.h>

int* bug_taintedmemoryallocsize(void) {
    size_t size;
    scanf("%zu", &size);
    int* p = (int*)malloc(size);//Noncompliant
    return p;
}

この例では、malloc がポインター p 用のメモリを size バイト割り当てます。変数 size はプログラムのユーザーから取得されます。その値がチェックされないため、使用可能なメモリ量を超える可能性があります。size が使用可能なバイト数を上回ると、プログラムがクラッシュする可能性があります。

修正 — 割り当てられるメモリのサイズをチェック

1 つの修正方法として、malloc 操作を実行する前に、割り当てるメモリのサイズをチェックします。この例では、size が正で、最大サイズより小さいかどうかを確認します。

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

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

int* corrected_taintedmemoryallocsize(void) {
    size_t size;
    scanf("%zu", &size);
    int* p = NULL;
    if (size>0 && size<SIZE128) {          /* Fix: Check entry range before use */
        p = (int*)malloc((unsigned int)size);
    }
    return p;
}
問題

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

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

リスク

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

修正方法

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

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

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

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

多くの場合、結果の詳細 (または 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 は割り当てられたメモリ ブロックの外を指しますが、もうデリファレンスされることはありません。

問題

この問題は、セキュリティで保護されていないソースからの値が、符号付きの値から符号なしの値に暗黙的または明示的に変換された場合に発生します。

たとえば、size_t を引数として使用する関数では、引数が暗黙的に符号なし整数に変換されます。size_t を暗黙的に変換する関数のいくつかを、以下に挙げます。

bcmp
memcpy
memmove
strncmp
strncpy
calloc
malloc
memalign

リスク

小さい負の数値を符号なしに変換すると、その結果は大きい正の数値となります。大きい正の数値により、セキュリティが脆弱になる可能性があります。たとえば、符号なしの値を以下で使用した場合がこれに該当します。

  • メモリ サイズ ルーチン — メモリ割り当ての問題の原因となります。

  • 文字列操作ルーチン — バッファー オーバーフローの原因となります。

  • ループ境界 — 無限ループの原因となります。

修正方法

符号なしの負の値の変換を避けるため、変換対象の値が許容範囲内にあることを確認します。たとえば、値がサイズを表す場合は、その値が負ではなく値の最大サイズより小さいことを検証します。

チェッカーの拡張

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

例 — メモリ値をサイズ引数で設定
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

void bug_taintedsignchange(void) {
    int size;
    scanf("%d",&size);
    char str[SIZE128] = "";
    if (size<SIZE128) {
        memset(str, 'c', size); //Noncompliant
    }
}

この例では、バッファー char が作成され、memset を使用して埋められます。memset のサイズ引数は、関数の入力引数です。

memset を呼び出すことにより、size が符号なし整数へと暗黙的に変換されます。size が大きい負の数値である場合、その絶対値は整数で表現するには大きすぎる可能性があり、バッファー オーバーフローの原因となります。

修正 — size の値をチェック

1 つの修正方法として、size が有効範囲内にあるかどうかをチェックします。この修正では、size がゼロより大きくバッファー サイズより小さいかどうかを、memset を呼び出す前にチェックします。

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

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

void corrected_taintedsignchange(void) {
    int size;
    scanf("%d",&size);
    char str[SIZE128] = "";
    if (size>0 && size<SIZE128) {
        memset(str, 'c', size);  
    }
}
問題

この問題は、可変長配列 (VLA) のサイズがセキュリティで保護されていないソースから取得された場合に発生します。

リスク

攻撃者が VLA のサイズを予期しない値に変更した場合、プログラムのクラッシュや予期しない動作の原因となることがあります。

サイズが正でない場合、VLA の動作は未定義となります。プログラムは想定どおりには実行されません。

サイズが無制限の場合、VLA はメモリ枯渇やスタック オーバーフローを引き起こすことがあります。

修正方法

VLA のサイズを検証して、正であり最大値より小さいことを確認します。

チェッカーの拡張

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

例 — VLA のサイズとして使用されるユーザー入力引数
#include<stdio.h>
#inclule<stdlib.h>
#define LIM 40

long squaredSum(int size) {

	int tabvla[size];  //Noncompliant
	long res = 0;
	for (int i=0 ; i<LIM-1 ; ++i) {
		tabvla[i] = i*i;
		res += tabvla[i];
	}
	return res;
}
int main(){
	int size;
	scanf("%d",&size);
	//...
	long result = squaredSum(size);
	//...
	return 0;
}

この例では、可変長配列のサイズが入力引数に基づいています。この入力引数の値はチェックされていないため、サイズが負になるか大きすぎる可能性があります。

修正 — VLA のサイズをチェック

1 つの修正方法として、可変長配列を作成する前にサイズ変数をチェックします。この例では、VLA を作成する前に、サイズが 0 より大きく 40 より小さいかどうかをチェックしています。

#include <stdio.h>
#include <stdlib.h>
#define LIM 40

long squaredSum(int size) {
	long res = 0;
	if (size>0 && size<LIM){
		int tabvla[size];
		for (int i=0 ; i<size || i<LIM-1 ; ++i) {
			tabvla[i] = i*i;
			res += tabvla[i];
		}
	}else{
		res = -1;
	}
	return res;
}
int main(){
	int size;
	scanf("%d",&size);
	//...
	long result = squaredSum(size);
	//...
	return 0;
}
問題

この問題は、以下の条件が両方当てはまる場合に発生します。

  1. メモリ ブロックのアドレスをポインターに代入するか、2 つのメモリ ブロック間でデータを転送します。この代入またはコピーで、sizeof 演算子を使用します。

    たとえば、malloc(sizeof(type)) を使用してポインターを初期化するか、memcpy(destination_ptr, source_ptr, sizeof(type)) を使用して 2 つのアドレス間でデータをコピーします。

  2. sizeof 演算子の引数に間違った型を使用します。次に例を示します。

    • ポインターが指す型ではなく、ポインター型を使用している可能性がある。たとえば、type* ポインターを初期化する際に、malloc(sizeof(type)) ではなく、malloc(sizeof(type*)) を使用している可能性があります。

    • sizeof 引数としてまったく無関係な型を使用している可能性がある。たとえば、type* ポインターを初期化する際に、malloc(sizeof(anotherType)) を使用している可能性があります。

リスク

type が何型でも、式 sizeof(type*) では常に固定サイズが返されます。返されるサイズは、お使いのプラットフォームでのポインターのサイズ (バイト単位) です。sizeof(type*) の出現は、多くの場合、意図とは異なる使い方を示しています。このエラーにより、必要とするよりもかなり小さなメモリ ブロックの割り当てになり、バッファー オーバーフローなどの脆弱性などにつながる可能性があります。

たとえば、structType が 10 個の int 変数を含む構造体だとします。32 ビット プラットフォームで malloc(sizeof(structType*)) を使用して structType* ポインターを初期化すると、ポインターには 4 バイトのメモリ ブロックが割り当てられます。しかし、1 つの structType 変数全体を割り当てるには、structType* ポインターは sizeof(structType) = 10 * sizeof(int) バイトのメモリ ブロックを指さなければなりません。必要なサイズは、実際に割り当てられた 4 バイトのサイズよりもはるかに大きなサイズです。

修正方法

type* ポインターを初期化するには、ポインターの初期化式の sizeof(type*)sizeof(type) に置き換えます。

例 — sizeof による文字配列の割り当て
#include <stdlib.h>

void test_case_1(void) {
    char* str;

    str = (char*)malloc(sizeof(char*) * 5);  //Noncompliant
    free(str);

}

この例では、5 つの文字ポインターの malloc を使用して文字ポインター str にメモリを割り当てています。しかし、str は文字を指すポインターであり、文字ポインターを指すポインターではありません。したがって、sizeof の引数 char* は正しくありません。

修正 — ポインターの型を sizeof の引数と一致させる

1 つの修正方法として、引数をポインターの型に一致させることができます。この例では、str は文字ポインターなので引数も文字でなければなりません。

#include <stdlib.h>

void test_case_1(void) {
    char* str;

    str = (char*)malloc(sizeof(char) * 5);
    free(str);

}

チェック情報

カテゴリ: Memory Buffer Errors

バージョン履歴

R2023a で導入