メインコンテンツ

再呼び出し不可能な標準関数の戻り値の不適切な使用

後続の呼び出しがそのバッファーを変更しているにもかかわらず、前の呼び出しからの静的バッファーへのポインターを使用している

説明

この欠陥は、次の事象がこの順番で起きた場合に発生します。

  1. getenvsetlocale など、再呼び出し不可能な標準関数から返されるバッファーをポイントします。

    user = getenv("USER");
  2. 再呼び出し不可能な標準関数をもう一度呼び出します。

    user2 = getenv("USER2");
  3. 最初のステップ以降バッファーが変更されずに残っていると想定して、最初のステップのポインターを使用またはデリファレンスします。しかし、バッファーは 2 番目のステップの呼び出しで変更されています。

    次に例を示します。

    var=*user;

場合によっては、関数 getenv を 2 回呼び出さなくてもポインターを返すだけで、欠陥が発生することがあります。次に例を示します。

char* func() {
     user=getenv("USER");
     .
     .
     return user;
}

この欠陥の対象となる関数の詳細は、再呼び出し不可能な標準関数のドキュメンテーションを参照してください。

リスク

C 標準では、getenv などの再呼び出し不可能な関数が "静的" バッファーへのポインターを返すことを許可しています。バッファーが静的であるため、getenv の 2 回目の呼び出しはバッファーを変更します。最初の呼び出しで返されたポインターを 2 回目の呼び出しの後も使用し続けると、予期しない結果になる可能性があります。ポイントされるバッファーは、最初の呼び出しの値ではなくなります。

関数 getenv を 2 度呼び出さなくてもポインターを返すだけで、この欠陥が発生します。それは、関数の呼び出し側が、getenv の 2 回目の呼び出しの "後で" 返されたポインターを使用することになるためです。getenv の呼び出しからポインターを返すことによって、関数は安全に使用できなくなります。

この欠陥の対象となる他の再呼び出し不可能な関数についても、同じ理由が当てはまります。

修正方法

getenv の最初の呼び出し後に、返されたポインターが指すバッファーをコピーします。getenv の 2 回目の呼び出し後は、このコピーを使用します。2 回目の呼び出しがバッファーを変更する場合でも、コピーには影響しません。

すべて展開する

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

int func()
{
    int result = 0;

    char *home = getenv("HOME");   /* First call */ 
    if (home != NULL) {
        char *user = NULL;
        char *user_name_from_home = strrchr(home, '/');
 
        if (user_name_from_home != NULL) {
            user = getenv("USER");   /* Second call */
            if ((user != NULL) &&
                (strcmp(user, user_name_from_home) == 0)) 
            {
                result = 1;
            }
        }
    }
    return result;
}

この例では、ポインター user_name_from_home はポインター home から派生しています。homegetenv の最初の呼び出しから返されるバッファーを指しています。そのため、user_name_from_home は同じバッファー内の場所を指します。

このバッファーは、getenv を再度呼び出した後に変更されます。user_name_from_home を引き続き使用すると、予期しない結果が発生する可能性があります。

修正 — 2 回目の呼び出し前にバッファーのコピーを作成

getenv の最初の呼び出しからのバッファーを 2 回目の呼び出し以降も使用する場合は、最初の呼び出し後にバッファーをコピーします。1 つの修正方法として、関数 strdup を使用してコピーします。

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

int func()
{
    int result = 0;

    char *home = getenv("HOME");    
    if (home != NULL) {
        char *user = NULL;
        char *user_name_from_home = strrchr(home, '/'); 
        if (user_name_from_home != NULL) {
            /* Make copy before second call */
            char *saved_user_name_from_home = strdup(user_name_from_home); 
            if (saved_user_name_from_home != NULL) {
                user = getenv("USER");  
                if ((user != NULL) &&
                    (strcmp(user, saved_user_name_from_home) == 0)) 
                {
                    result = 1;
                }
                free(saved_user_name_from_home);
            }
        }
    }
    return result;
}

結果情報

グループ: プログラミング
言語: C | C++
既定値: オフ
コマンド ライン構文: NON_REENTRANT_STD_RETURN
影響度: High

バージョン履歴

R2017a で導入