メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

標準ライブラリ関数呼び出しでデータ レースが発生しました

スレッドセーフでない標準ライブラリ関数に対して、複数のタスクから保護されていない呼び出しが行われています。

説明

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください

この欠陥は、以下の場合に発生します。

  1. 複数のタスクから同じ標準ライブラリ関数を呼び出す。

    たとえば、複数のタスクから strerror 関数を呼び出します。

  2. 呼び出しが共通の保護を使って保護されていない。

    たとえば、呼び出しが同じクリティカル セクションによって保護されていません。

この欠陥によってフラグが付けられた関数は、再呼び出し可能であることは保証されません。前の呼び出しが実行を完了する前に、割り込まれたり、安全に再度呼び出されることができる場合、関数は再呼び出し可能です。関数が再呼び出し可能ではない場合、複数のタスクからその関数を保護なしで呼び出すと、同時実行の問題の原因となる可能性があります。フラグが付けられた関数のリストについては、次を参照してください。CON33-C:Avoid race conditions when using library functions

この欠陥を検出するには、解析前にマルチタスキング オプションを指定しなくてはなりません。[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細は、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

この欠陥によってフラグが付けられた関数は、その実装でグローバル変数や静的変数が使用される可能性があるため、再呼び出し可能ではありません。複数のタスクから保護なしでその関数を呼び出すと、一方のタスクからの関数呼び出しが別のタスクからの呼び出しに干渉する可能性があります。その関数の 2 つの呼び出しがグローバル変数や静的変数に同時にアクセスし、予期しない結果が生じる可能性があります。

この呼び出しが、異常終了、サービス拒否攻撃、データの整合性違反など、より深刻なセキュリティの脆弱性の原因となることもあります。

修正方法

この欠陥を修正するには、以下のいずれかを行います。

  • 標準ライブラリ関数の再呼び出し可能なバージョンが存在する場合はそちらを使用します。

    たとえば、strerror() ではなく、strerror_r() または strerror_s() を使用します。この欠陥によってフラグが付けられた関数の代替方法については、「CON33-C」を参照してください。

  • 共通のクリティカル セクションまたは時間的に排他を使用して、その関数呼び出しを保護します。

    クリティカル セクション詳細 (-critical-section-begin -critical-section-end)および時間的に排他なタスク (-temporal-exclusions-file)を参照してください。

    再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。以下の例を参照してください。

すべて展開する

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

この例では、マルチタスクの動作をエミュレートするため、以下のオプションを指定しなければなりません。

オプション仕様
マルチタスクを手動で構成
タスク (-entry-points)

task1

task2

task3

クリティカル セクション詳細 (-critical-section-begin -critical-section-end)開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例では、タスク task1task2 および task3 が関数 func を呼び出します。関数 func は再呼び出し可能ではない標準ライブラリ関数 strerror を呼び出します。

task3 はクリティカル セクションの内部で func を呼び出しますが、他のタスクは同じクリティカル セクションを使用していません。task3 のクリティカル セクション内の操作は、他のタスクでの操作と相互に排他的ではありません。

これらの 3 つのタスクは、共通の保護を使用しないで再呼び出し可能ではない標準ライブラリ関数を呼び出しています。結果の詳細で、競合する関数呼び出しの各ペアが表示されます。

アイコンをクリックすると、標準ライブラリ関数呼び出しへのエントリ ポイントから始まる関数呼び出し順序が表示されます。task3 から始まる呼び出しがクリティカル セクションに含まれていることも確認できます。[アクセス保護] エントリでは、クリティカル セクションを開始、終了するロック関数とロック解除関数が表示されます。この例では、関数 begin_critical_sectionend_critical_section が表示されます。

修正 - 標準ライブラリ関数の再呼び出し可能なバージョンを使用

考えられる 1 つの修正方法として、標準ライブラリ関数 strerror の再呼び出し可能なバージョンを使用します。POSIX® バージョンの strerror_r を使用できます。これには同じ機能がありますが、スレッド セーフ性が保証されます。

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);
enum { BUFFERSIZE = 64 };

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char errmsg[BUFFERSIZE];
    if (strerror_r(errno, errmsg, BUFFERSIZE) != 0) {
      /* Handle error */
    }
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}
修正 — クリティカル セクション内に関数呼び出しを配置

考えられる 1 つの修正方法として、strerror の呼び出しをクリティカル セクション内に配置します。クリティカル セクションは複数の方法で実装できます。

たとえば、中間関数 func の呼び出しを 3 つのタスクの同じクリティカル セクション内に配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。func の呼び出し、つまり 3 つのタスクからの strerror の呼び出しは互いに干渉する可能性はありません。

クリティカル セクションを実装するには、3 つのそれぞれのタスク内で begin_critical_sectionend_critical_section の呼び出しの間に func を呼び出します。

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    begin_critical_section();
    func(fptr1);
    end_critical_section();
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     begin_critical_section();
     func(fptr2);
     end_critical_section();
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

修正 — タスクを時間的に排他にする

別の修正方法として、タスク task1task2 および task3 を時間的に排他にします。時間的に排他なタスクは同時に実行することはできません。

[構成] ペインで、以下の追加オプションを指定します。

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder
     -temporal-exclusions-file "C:\exclusions_file.txt"
ここで、ファイル C:\exclusions_file.txt には以下の行があります。
task1 task2 task3

結果情報

グループ: 同時実行
言語: C | C++
既定値: オン
コマンド ライン構文: DATA_RACE_STD_LIB
影響度: High

バージョン履歴

R2016b で導入