メインコンテンツ

CWE Rule 468

ポインターのスケーリングが無効です

R2023a 以降

説明

ルールの説明

In C and C++, one may often accidentally refer to the wrong memory due to the semantics of when math operations are implicitly scaled.

Polyspace 実装

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

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

  • ポインターのスケーリングが無効です

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

  • 異なる配列を指すポインター間の減算または比較

すべて展開する

問題

この問題は、配列へのアクセス中に配列のインデックスが範囲 [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® Bug Finder™ によって、ポインター演算での暗黙的なスケーリングが無視されているとみなされた場合に発生します。

たとえば、欠陥は次のような状況で発生します。

状態リスク考えられる解決方法
ポインターでの算術演算で sizeof 演算子を使用する。

sizeof 演算子が、データ型のサイズをバイト数で返す。

ポインター演算は既に、指されている変数のデータ型のサイズによって暗黙的にスケーリングされている。そのため、ポインター演算で sizeof を使用すると想定外の結果が生成される。

ポインター演算で sizeof 演算子を使用しない。
ポインターに対し算術演算を実行してから、キャストを適用する。ポインター演算は暗黙的にスケーリングされている。この暗黙的なスケーリングを考慮せずにポインター演算の結果をキャストすると、想定外の結果が生成される。キャストをポインター演算の前に適用する。

修正方法

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

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

例 — sizeof 演算子の使用
void func(void) {
    int arr[5] = {1,2,3,4,5};
    int *ptr = arr;

    int value_in_position_2 = *(ptr + 2*(sizeof(int)));  //Noncompliant
}

この例では、演算 2*(sizeof(int)) で変数 int の 2 倍のサイズ (バイト単位) が返されます。ただし、ポインター演算は暗黙的にスケーリングされているため、ptr がオフセットされるバイト数は 2*(sizeof(int))*(sizeof(int)) となります。

この例では、不正確なスケーリングにより、ptr が配列の境界外にシフトされます。したがって、[範囲外にアクセスするポインター] エラーが * 演算に表示されます。

修正 — sizeof 演算子を削除

1 つの修正方法として、sizeof 演算子を削除します。

void func(void) {
    int arr[5] = {1,2,3,4,5};
    int *ptr = arr;

    int value_in_position_2 = *(ptr + 2);
}
例 — ポインター演算後のキャスト
int func(void) {
    int x = 0;
    char r = *(char *)(&x + 1);  //Noncompliant
    return r;
}

この例では、演算 &x + 1 により &xsizeof(int) の分オフセットされます。演算後、その結果であるポインターは、許容されているバッファーの外を指しています。ポインターをデリファレンスすると、[範囲外にアクセスするポインター] エラーが * 演算に表示されます。

修正 — キャストをポインター演算の前に適用

x の 2 番目のバイトにアクセスする場合は、まず &xchar* ポインターにキャストしてからポインター演算を実行します。その結果得られるポインターは、sizeof(char) バイト分オフセットされたうえで、サイズが sizeof(int) バイトの許容されているバッファー内を指しています。

int func(void) {
    int x = 0;
    char r = *((char *)(&x )+ 1);
    return r;
}
問題

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

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

リスク

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

修正方法

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

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

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

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

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

問題

この問題は、null ポインターまたは別の配列内の要素を指しているポインターを減算または比較した場合に発生します。比較の関係演算子は ><>=、および <= です。

リスク

同じ配列内の要素を指す 2 つのポインターを減算する場合、結果は 2 つの配列要素の添字の間の差になります。同様に、配列の要素を指す 2 つのポインターを比較する場合、結果は相対的なポインターの位置になります。ポインターが null または異なる配列の要素を指している場合、減算や比較演算は未定義です。減算結果をバッファー インデックスとして使用する場合、バッファー オーバーフローが発生する可能性があります。

修正方法

配列要素を指すポインターの減算や比較演算子による比較を行う前に、ポインターが非 null であり同じ配列を指していることをチェックします。

例 — 異なる配列内の要素を指すポインター間の減算
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE20 20

size_t func(void)
{
    int nums[SIZE20];
    int end;
    int *next_num_ptr = nums;
    size_t free_elements;
	/* Increment next_num_ptr as array fills */
	
	/* Subtraction operation is undefined unless array nums 
	is adjacent to variable end in memory. */
    free_elements = &end - next_num_ptr;  //Noncompliant
    return free_elements;
}
    
      

この例では、配列 nums がインクリメントされて埋めらます。その後、空の要素がいくつ残っているか判断するためにポインターの減算が使用されます。endnums の最後の要素を超えたメモリ位置を指していない限り、減算演算は未定義です。

修正 — 同じ配列を指すポインターを減算

配列の最後の要素を指すポインターから埋められた最後の要素を指すポインターを減算します。

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

#define SIZE20 20

size_t func(void)
{
    int nums[SIZE20];
    int *next_num_ptr = nums;
    size_t free_elements;
	/* Increment next_num_ptr as array fills */
	
	/* Subtraction operation involves pointers to the same array. */
    free_elements = &(nums[SIZE20 - 1]) - next_num_ptr;  
	
    return free_elements + 1;
}
     

チェック情報

カテゴリ: Pointer Issues

バージョン履歴

R2023a で導入