メインコンテンツ

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

CERT C: Rule ERR33-C

標準ライブラリのエラーを検出および処理する

説明

ルール定義

標準ライブラリのエラーを検出および処理します。1

Polyspace 実装

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

  • errno の未チェック

  • 要注意の標準関数の戻り値がチェックされていない

  • 保護されていない動的メモリ割り当て

  • 再割り当て中に上書きされるポインター

すべて展開する

問題

errno の未チェックは、エラー状態を示すために errno を設定する関数を呼び出しても、その呼び出し後に errno をチェックしていない場合に発生します。このような関数の場合、エラーが発生したかどうかを判断するための唯一信頼できる方法は errno をチェックすることです。

エラーで errno を設定する関数には次のものがあります。

リスク

エラーなしで関数呼び出しが完了したかどうかを確認するには、errno のエラー値をチェックします。

このような errno を設定する関数の戻り値はエラーを示しません。戻り値は、以下のいずれかになります。

  • void

  • エラーが発生しても、戻り値は正常に行われた呼び出しと同じ値になる可能性があります。このような戻り値をインバンド エラー インジケーターと呼びます。

エラーが発生したかどうかは、errno をチェックすることでのみ判断できます。

たとえば、strtol は文字列を long 型整数に変換して、その整数を返します。変換結果がオーバーフローする場合、この関数は LONG_MAX を返し、errnoERANGE を設定します。ただし、この関数は正常に変換が行われても LONG_MAX を返すことがあります。errno をチェックすることでのみ、エラーと正常な変換を区別することができます。

修正方法

関数を呼び出す前に、errno にゼロを設定します。

関数呼び出しの後、errno をゼロと比較して、エラーが発生したかどうかを確認します。あるいは、errno を既知のエラー インジケーター値と比較します。たとえば、strtolerrnoERANGE を設定してエラーを示します。

Polyspace® の結果のエラー メッセージには、比較できるエラー インジケーター値が示されます。

例 - strtol 呼び出し後の errno 未チェック
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main(int argc, char *argv[]) {
    char *str, *endptr;
    int base;
    
    str = argv[1];
    base = 10;
    
    long val = strtol(str, &endptr, base); //Noncompliant
    printf("Return value of strtol() = %ld\n", val);
}

errno をチェックしないで strtol の戻り値を使用しています。

修正 — 呼び出し後に errno をチェック

strtol を呼び出す前に、errno にゼロを設定します。strtol 呼び出し後、戻り値については LONG_MIN または LONG_MAX をチェックし、errno については ERANGE をチェックします。

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

int main(int argc, char *argv[]) {
    char *str, *endptr;
    int base;
    
    str = argv[1];
    base = 10;
    
    errno = 0;
    long val = strtol(str, &endptr, base);
    if((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE) {
         printf("strtol error");
         exit(EXIT_FAILURE);
    }        
    printf("Return value of strtol() = %ld\n", val);
}
問題

"要注意の標準関数の戻り値がチェックされていない" という問題は、要注意の標準関数を呼び出すときに、以下を行うと発生します。

  • 戻り値を無視。

  • 戻り値の妥当性をテストすることなく出力または戻り値を使用。

この欠陥では、"要注意""重要で要注意" という 2 つのタイプの関数が考慮されます。

"要注意" の関数とは、以下に遭遇する可能性のある標準関数です。

  • システム リソースが使い尽くされた状態 (リソースを割り当てるときなど)

  • 権限またはアクセス許可が変更された状態

  • 外部ソースからのデータを読み取り、書き込みまたは変換する際にソースが汚染された状態

  • 既存の API があってもサポートされない機能

"重要で要注意" の関数とは、次の重要または脆弱なタスクのいずれかを実行する要注意関数です。

  • 権限の設定 (setuid など)

  • jail の作成 (chroot など)

  • プロセスの作成 (fork など)

  • スレッドの作成 (thrd_create など)

  • メモリ セグメントのロックまたはロック解除 (mlock など)

リスク

要注意または重要で要注意のタスクを実行する関数の戻り値をチェックしない場合、プログラムが予期しない動作をする可能性があります。これらの関数のエラーはプログラム全体に伝播し、不適切な出力、セキュリティの脆弱性およびシステム障害の原因となる可能性があります。

修正方法

プログラムを続行する前に、"重要で要注意" の関数の戻り値をテストします。

"要注意の関数" の場合、関数を void にキャストすることで戻り値を明示的に無視することができます。Polyspace では、要注意の関数が void をキャストしている場合、欠陥とは見なされません。"重要で要注意の関数" の場合、さらに脆弱なタスクが実行されるため、この解決策は許容されません。

例 - 要注意の関数の戻り値が無視される
#include<stdio.h>
#include <wchar.h>
#include <locale.h>
void initialize(size_t n, size_t* size, wchar_t *wcs, const char *utf8) {

	scanf("%d",&n); //Noncompliant
	setlocale (LC_CTYPE, "en_US.UTF-8");   //Noncompliant
	*size = mbstowcs (wcs, utf8, n);
}

この例は、要注意関数 scanf() の呼び出しを示します。scanf() の戻り値が無視され、欠陥の原因になっています。同様に、setlocale から返されるポインターもチェックされません。setlocal から NULL ポインターが返されると、mbstowcs の呼び出しが失敗するか、または予期しない結果が生じる可能性があります。Polyspace は、要注意の関数の戻り値がチェックされない場合、これらの関数の呼び出しにフラグを設定します。

修正 - 関数を (void) にキャスト

1 つの修正方法として、関数を void にキャストします。この修正では Polyspace およびレビュー担当者に、これらの要注意の関数の戻り値を明示的に無視していることを伝えます。

#include<stdio.h>
#include <wchar.h>
#include <locale.h>
void initialize(size_t n, size_t* size, wchar_t *wcs, const char *utf8) {

	(void)scanf("%d",&n); //Compliant
	(void)setlocale (LC_CTYPE, "en_US.UTF-8");   //Compliant
	*size = mbstowcs (wcs, utf8, n);
}
修正 — 戻り値をテスト

1 つの修正方法として、scanfsetlocale の戻り値をテストして、エラーをチェックします。

#include<stdio.h>
#include <wchar.h>
#include <locale.h>
void initialize(size_t n, size_t* size, wchar_t *wcs, const char *utf8) {

	int flag = scanf("%d",&n); 
	if(flag>0){ //Compliant
		// action
	}
	char* status = setlocale (LC_CTYPE, "en_US.UTF-8");   
	if(status!=NULL){//Compliant
		*size = mbstowcs (wcs, utf8, n);
	}
	
}
例 - 重要な関数の戻り値が無視される
#include <threads.h>
int thrd_func(void);
void initialize() {
    thrd_t thr;
	int n = 1;

    (void) thrd_create(&thr,thrd_func,&n);  //Noncompliant
}

この例では、重要な関数 thrd_create が呼び出され、戻り値が void にキャストされて無視されますが、thrd_create は重要な関数なので Polyspace はこの欠陥を無視しません。

修正 - 重要な関数の戻り値をテスト

この欠陥の修正として、これらの重要な関数の戻り値をチェックして、関数が想定どおりに実行されることを検証します。

#include <threads.h>
int thrd_func(void);
void initialize() {
	thrd_t thr;
	int n = 1;
	if( thrd_success!= thrd_create(&thr,thrd_func,&n) ){
		// handle error
		
	}
}
問題

保護されていない動的メモリ割り当ては、動的メモリ割り当ての後にメモリ割り当てが成功したかどうかをチェックしていない場合に発生します。

リスク

malloccalloc または realloc を使用してメモリを動的に割り当てる際、要求されたメモリが使用可能でない場合は値 NULL が返されます。割り当ての後、コードでこの NULL 値をチェックせずにメモリ ブロックにアクセスした場合、このアクセスの成功は保証されません。

修正方法

割り当てられたメモリ位置にアクセスする前に、malloccalloc、または realloc の戻り値が NULL かどうかをチェックします。

#DEFINE SIZE 8;

int *ptr = malloc(SIZE * sizeof(int));

if(ptr) /* Check for NULL */ 
{
   /* Memory access through ptr */
}

例 - 保護されていない動的メモリ割り当てエラー
#include <stdlib.h>

void Assign_Value(void) 
{
  int* p = (int*)calloc(5, sizeof(int));
  *p = 2;  //Noncompliant  
  /* Defect: p is not checked for NULL value */
  free(p); 
} 
/*Defect: p is not checked for NULL before deallocating*/

メモリ割り当てが失敗した場合、calloc などの関数は NULLp に返します。p を使用してメモリにアクセスする前、または p を解放する前に、コードでは pNULL かどうかのチェックが行われていません。このような操作によってメモリ リークが発生する可能性があります。

修正 — null 値をチェック

1 つの修正方法として、デリファレンスの前に p の値が NULL かどうかをチェックすることができます。

#include <stdlib.h>

void Assign_Value(void)
 {
   int* p = (int*)calloc(5, sizeof(int));
   /* Fix: Check if p is NULL */
   if(p!=NULL) *p = 2; 
   free(p);
 }
問題

"再割り当て中に上書きされるポインター" は、元のポインターが realloc() の戻り値によって上書きされるときに発生します。次に例を示します。

p = realloc(p,SIZE);

リスク

関数 realloc() は、メモリ割り当てが失敗すると NULL 値を返します。前述のコードでは、realloc() の戻り値により p を上書きするため、再割り当て操作が失敗するときに NULL になります。元のメモリ ブロックと p の間のつながりが失われ、メモリ リークが発生します。

修正方法

ポインターの再割り当て時に、元のポインターを維持します。たとえば、一時変数を使用して、再割り当てされたメモリを格納することがあります。

例 — メモリの再割り当て時に元のポインターの上書きを回避する
#include <stdlib.h>
//...
void foo (int* ptrI, size_t new_size)
{

  if (new_size == 0) {
    /* Handle error */
    return;
  }

  ptrI = (int*)realloc (ptrI, new_size);   //Noncompliant

  if (ptrI == NULL) {
    /* Handle error */
    return;
  }
}

ポインター ptrI を、realloc により返されるポインターで上書きすると、ptrI と元のメモリ ブロックの関連付けが失われます。realloc が失敗した場合、このような上書きが原因でメモリ リークやデータ損失が引き起こされる可能性があります。

修正 — 再割り当てされたメモリを一時変数に格納する

ポインターを再割り当てするときに、一時変数を使用して、再割り当てされたメモリを維持します。一時変数を ptrI に代入する前に、これに NULL 値があるかどうかを確認することで、メモリ リークとデータ損失を回避します。

#include <stdlib.h>

void foo (int* ptrI, size_t new_size)
{
int* temp;
  if (new_size == 0) {
    /* Handle error */
    return;
  }

  temp = (int*)realloc (ptrI, new_size);

  if (temp == NULL) {
    /* Handle error */
    return;
  }else{
	  ptrI = temp;
  }
}

チェック情報

グループ: Rule 12.エラーの取り扱い (ERR)

バージョン履歴

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.