メインコンテンツ

CERT C: Rec.EXP08-C

Ensure pointer arithmetic is used correctly

説明

ルール定義

ポインター演算を適切に使用するようにします。1

Polyspace 実装

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

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

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

  • 異なる配列を指すポインター間の減算

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

チェッカーの拡張

入力値が不明であり、入力のサブセットのみがエラーの原因となっている場合、既定の Bug Finder 解析では "配列が範囲外にアクセス" 問題にフラグを設定しない場合があります。特定のシステム入力値を原因とする "配列が範囲外にアクセス" 問題の有無をチェックするには、より厳密な Bug Finder 解析を実行してください。特定のシステム入力値から欠陥を見つけるための Bug Finder チェッカーの拡張を参照してください。

すべて展開する

問題

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

リスク

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

修正方法

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

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

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

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

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

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

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

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

例 – 配列が範囲外にアクセス エラー
#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 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 は割り当てられたメモリ ブロックの外を指しますが、もうデリファレンスされることはありません。

問題

このルールは、解析で異なる配列を指すポインター間の減算または比較が検出された場合に適用されます。

リスク

このルールは、pointer_expression1 - pointer_expression2 形式の式に適用されます。pointer_expression1 および pointer_expression2 が次のいずれかである場合、動作は未定義になります。

  • 同じ配列の要素を指していない。

  • 配列の終端を超えた要素を指していない。

例 - ポインターの減算
#include <stdint.h>
#include <stddef.h>

void f1 (int32_t *ptr)
{
    int32_t a1[10];
    int32_t a2[10];
    int32_t *p1 = &a1[ 1];
    int32_t *p2 = &a2[10];
    ptrdiff_t diff1, diff2, diff3;

    diff1 =  p1 - a1;   // Compliant
    diff2 =  p2 - a2;   // Compliant
    diff3 =  p1 - p2;   // Non-compliant
}

この例では、3 つの減算式で、ポインターの減算が準拠か非準拠かの相違が示されます。減算 diff1 および diff2 は、ポインターが同じ配列を指しているので準拠しています。減算 diff3 は、p1p2 が異なった配列を指しているため準拠していません。

問題

ポインターのスケーリングが無効ですは、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;
}

チェック情報

グループ: Rec.03.式 (EXP)

バージョン履歴

R2019a で導入

すべて展開する


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.