メインコンテンツ

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

CWE Rule 690

Unchecked Return Value to NULL Pointer Dereference

R2023a 以降

説明

ルールの説明

The product does not check for an error after calling a function that can return with a NULL pointer if the function fails, which leads to a resultant NULL pointer dereference.

Polyspace 実装

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

  • 要注意の関数の戻り値がチェックされていません

  • 汚染された NULL 文字列または非 NULL 終端文字列

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

すべて展開する

問題

この問題は、エラー発生の可能性に関する情報を返す要注意の標準関数を呼び出し、以下のいずれかを行った場合に発生します。

  • 戻り値を無視。

    戻り値を変数に単純に代入することも、戻り値を明示的に void にキャストすることもしていない。

  • エラーの戻り値をテストすることなく関数の出力 (戻り値または引数の参照渡し) を使用。

以下のような理由で関数呼び出しで障害を発生する可能性が高い場合、チェッカーはその関数を要注意と見なします。

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

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

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

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

チェッカーは、関数がエラーなしで終了したかどうかを "戻り値" が示す関数のみを考慮します。

このような関数の一部では、以下のような重要なタスクを実行する可能性があります。

  • 権限の設定 (setuid など)

  • jail の作成 (chroot など)

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

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

  • ミューテックスのロックまたはロック解除 (pthread_mutex_lock など)

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

リスク

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

修正方法

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

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

例 — 要注意の関数の戻り値が無視される
#include <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

void initialize() {
    pthread_attr_t attr;

    pthread_attr_init(&attr);//Noncompliant 
}
int read_file(int argc, char *argv[])
{
  FILE *in;
  if (argc != 2) {
    /* Handle error */
  }

  in = fmemopen (argv[1], strlen (argv[1]), "r");   
  return 0; //Noncompliant

}

この例は、要注意の POSIX 関数 pthread_attr_init および fmemopen の呼び出しを示しています。それらの戻り値が無視され、欠陥の原因になっています。

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

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

#include <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

void initialize() {
    pthread_attr_t attr;

    (void)pthread_attr_init(&attr);//Compliant 
}
int read_file(int argc, char *argv[])
{
  FILE *in;
  if (argc != 2) {
    /* Handle error */
  }

  (void)fmemopen (argv[1], strlen (argv[1]), "r"); //Compliant
  
  return 0; 
}
修正 — 戻り値をテスト

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

#include <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

void initialize() {
    pthread_attr_t attr;

    int result = pthread_attr_init(&attr);//Compliant 
	if(result != 0){
		//Handle fatal error
	} 
}
int read_file(int argc, char *argv[])
{
  FILE *in;
  if (argc != 2) {
    /* Handle error */
  }

  in = fmemopen (argv[1], strlen (argv[1]), "r"); 
  if (in==NULL){
	  // Handle error
  }
  return 0;//Compliant 
}
例 — 重要な関数の戻り値が無視される
#include <pthread.h>
extern void *start_routine(void *);

void returnnotchecked() {
    pthread_t thread_id;
    pthread_attr_t attr;
    void *res;

    (void)pthread_attr_init(&attr);
    (void)pthread_create(&thread_id, &attr, &start_routine, ((void *)0));  //Noncompliant
    pthread_join(thread_id,  &res);  //Noncompliant
}

この例では、2 つの重要な関数 pthread_create および pthread_join を呼び出しています。pthread_create の戻り値は void にキャストすることで無視されますが、pthread_create は (要注意の関数であるだけでなく) 重要な関数であるため、Polyspace ではこの "要注意の関数の戻り値がチェックされていません" という欠陥が無視されません。他の重要な関数 pthread_join は暗黙的に無視される値を返します。pthread_join は、pthread_create の戻り値を使用しますが、この戻り値はチェックされていません。

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

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

#include <pthread.h>
#include <stdlib.h>
#define fatal_error() abort()

extern void *start_routine(void *);

void returnnotchecked() {
    pthread_t thread_id;
    pthread_attr_t attr;
    void *res;
    int result;

    (void)pthread_attr_init(&attr);
    result = pthread_create(&thread_id, &attr, &start_routine, NULL);
    if (result != 0) {
        /* Handle error */
        fatal_error();
    }

    result = pthread_join(thread_id,  &res);
    if (result != 0) {
        /* Handle error */
        fatal_error();
    }
}
問題

この問題は、strcpysprintf などの文字列バッファーを暗黙的にデリファレンスする文字列操作ルーチンで、セキュリティで保護されていないソースからの文字列が使用された場合に発生します。

汚染された NULL 文字列または非 NULL 終端文字列では、scanf ファミリの可変個引数関数の呼び出しから返された文字列に関する欠陥が報告されません。同様に、文字列と一緒に %s 指定子を printf ファミリの可変個引数関数に渡した場合も欠陥は報告されません。

リスク

文字列がセキュリティで保護されないソースに由来している場合、攻撃者により文字列が操作されている可能性や、文字列ポインターが異なるメモリ位置に向けられている可能性があります。

文字列が NULL である場合、文字列ルーチンは文字列をデリファレンスできず、プログラムがクラッシュする原因となります。文字列が null で終了しない場合、文字列ルーチンでは文字列がいつ終了するかわからない可能性があります。このエラーは範囲外への書き込みの原因となり、バッファー オーバーフローを引き起こします。

修正方法

文字列は、使用する前に検証します。以下についてチェックします。

  • 文字列が NULL でない。

  • 文字列が null で終了している。

  • 文字列のサイズが、必要なサイズと一致している。

チェッカーの拡張

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

例 — 文字列を入力から取得
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
void warningMsg(void)
{
	char userstr[MAX];
	int n = read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (n != -1)
         strncat(str, userstr, SIZE128-(strlen(str)+1));//Noncompliant
	print_str(str);
}


この例では、文字列 str は引数 userstr と連結しています。userstr の値は不明です。userstr のサイズが使用可能なスペースより大きい場合、この連結はオーバーフローします。

修正 — データを検証

1 つの修正方法として、strncat で使用する前に、userstr のサイズをチェックして、文字列が必ず null で終了するようにします。この例では、補助関数 sansitize_str を使用して文字列を検証しています。欠陥はこの関数に集中しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
int sanitize_str(char* s) {
	int res = 0; 
	if (s && (strlen(s) > 0)) { // Noncompliant
		// - string is not null
		// - string has a positive and limited size
		// - TAINTED_STRING on strlen used as a firewall
		res = 1;
	}
	return res; 
}
void warningMsg(void)
{
	char userstr[MAX];
	int n = read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (n != -1 && sanitize_str(userstr))	
		strncat(str, userstr, SIZE128-(strlen(str)+1));
	print_str(str);
}
修正 — データを検証

別の修正方法として、特定の文字列を含む関数 errorMsg および warningMsg を呼び出します。

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

#define SIZE128 128

extern void print_str(const char*);

void warningMsg(char* userstr)
{
    char str[SIZE128] = "Warning: ";
    strncat(str, userstr, SIZE128-(strlen(str)+1));
    print_str(str);
}

void errorMsg(char* userstr)
{
  char str[SIZE128] = "Error: ";
  strncat(str, userstr, SIZE128-(strlen(str)+1));
  print_str(str);
}

int manageSensorValue(int sensorValue) {
  int ret = sensorValue;
  if ( sensorValue < 0 ) {
    errorMsg("sensor value should be positive");
    exit(1);
  } else if ( sensorValue > 50 ) {
    warningMsg("sensor value greater than 50 (applying threshold)...");
    sensorValue = 50;
  }
  
  return sensorValue;
}
問題

この問題は、前回のメモリ割り当てが成功したかどうかを初めにチェックせずに、動的に割り当てられたメモリにアクセスしたときに発生します。

リスク

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

修正方法

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

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);
}

メモリ割り当てが失敗した場合、関数 callocNULLp に返します。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);
 }
例 — デリファレンス時のみの保護されていない動的メモリ割り当てエラー
#include <stdlib.h>
#include<string.h>
typedef struct recordType {
    const char* id;
    const char* data;
} RECORD;

RECORD* MakerecordType(const char *id,unsigned int size){
    RECORD *rec = (RECORD *)calloc(1, sizeof(RECORD));
    rec->id = strdup(id);  //Noncompliant

    const char *newData = (char *)calloc(1, size);
    rec->data = newData;
    return rec;
}

この例では、前回の動的メモリ割り当てからの NULL 値をチェックせずにポインター rec がデリファレンスされたときに、チェッカーが欠陥を報告します。

同様の問題がポインター newData でも発生します。ただし、ポインターはデリファレンスされませんが、rec->data にコピーされるだけなので、欠陥は報告されません。null の可能性があるポインターにコピーするだけでは問題になりません。たとえば、関数 recordType_new の呼び出し元がデリファレンスの前に rec->dataNULL 値をチェックすれば、null ポインターのデリファレンスは回避されます。

チェック情報

カテゴリ: その他

バージョン履歴

R2023a で導入