JPCERT コーディネーションセンター

ENV31-C. 環境変数へのポインタを無効にするかもしれない操作の後で、そのポインタを参照しない

環境によっては main() が呼び出された時点で有効な環境変数へのポインタが提供されるが、そのポインタは環境を変更するなんらかの操作を行うと無効になる場合がある。

C 標準 [ISO/IEC 9899:2011] セクション J.5.1 には次のように記載されている。

ホスト環境において、main 関数は第3引数 char *envp[] をとる。この引数は char へのポインタの null 終端配列を指す。char への各ポインタは、このプログラム実行環境に関する情報を提供する文字列を指す。

したがって、ホスト環境においては、次のように main() の形式を変えることで環境変数にアクセスすることができる。

main(int argc, char *argv[], char *envp[])

しかし、何らかの方法で環境に変更を加えると、環境のメモリ領域が再割り当てされることになり、結果として envp が間違った場所を参照することになる場合がある。

たとえば、Intel 32 ビットの GNU/Linux 上で GCC 3.4.6 を使って以下のコードをコンパイルし、実行する。

extern char **environ;

/* ... */

int main(int argc, const char *argv[], const char *envp[]) {
   printf("environ:  %p\n", environ);
   printf("envp:     %p\n", envp);
   setenv("MY_NEW_VAR", "new_value", 1);
   puts("--Added MY_NEW_VAR--");
   printf("environ:  %p\n", environ);
   printf("envp:     %p\n", envp);
}

これは以下の結果を返す。

% ./envp-environ
environ: 0xbf8656ec
envp: 0xbf8656ec
--Added MY_NEW_VAR--
environ: 0x804a008
envp: 0xbf8656ec

この結果からも明らかなように、setenv() を呼び出した結果、環境のメモリ領域が再割り当てされている。

違反コード (POSIX)

POSIX setenv() やその他の環境に変更を加える関数を呼び出した後、envp は環境を参照しなくなっている可能性がある。POSIX によると[Open Group 2004]

setenv() が外部変数 environ を変更すると、想定されていない結果が生じる可能性がある。特に、main() のオプションの envp 引数が存在する場合、それは変更されず、結果的に環境の古いコピーの1つを指す可能性がある(他のenviron のコピーも同様)。

このコードでは setenv() を呼び出したあとで envp ポインタにアクセスしている。

int main(int argc, const char *argv[], const char *envp[]) {
   size_t i;
   if (setenv("MY_NEW_VAR", "new_value", 1) != 0) {
     /* エラー処理 */
   }
   if (envp != NULL) {
      for (i = 0; envp[i] != NULL; i++) {
         if (puts(envp[i]) == EOF) {
           /* エラー処理 */
         }
      }
   }
   return 0;
}

envp は現在の環境を指しておらず、プログラムの動作は未定義となる。

適合コード (POSIX)

envp の代わりに environ が定義されている場合、それを利用する。

extern char **environ;

/* ... */

int main(int argc, const char *argv[]) {
   size_t i;
   if (setenv("MY_NEW_VAR", "new_value", 1) != 0) {
     /* エラー処理 */
   }
   if (environ != NULL) {
      for (i = 0; environ[i] != NULL; i++) {
         if (puts(environ[i]) == EOF) {
           /* エラー処理 */
         }

      }
   }
   return 0;
}
違反コード (Windows)

Windows の _putenv_s() 関数やその他の環境に変更を加える関数の呼び出した後では、envp はもはや環境を参照していないかもしれない。

Visual C++ リファレンスによると [MSDN]

mainwmain に渡される環境ブロックは、現在の環境の"frozen"コピーである。_putenv_wputenv を呼び出して環境に変更を加えると、現在の環境(getenv / _wgetenv_environ / _wenviron 変数が返す)は変更されるが、envp が参照するブロックは変更されない。

このコードでは、_putenvs() を呼び出した後で envp ポインタにアクセスしている。

int main(int argc, const char *argv[], const char *envp[]) {
   size_t i;
   if (_putenv_s("MY_NEW_VAR", "new_value") != 0) {
     /* エラー処理 */
   }
   if (envp != NULL) {
      for (i = 0; envp[i] != NULL; i++) {
         if (puts(envp[i]) == EOF) {
           /* エラー処理 */
         }
      }
   }
   return 0;
}

envp は現在の環境を指していないので, このプログラムでは MY_NEW_VAR の値を表示できない。

適合コード (Windows)

_environ が定義されている場合、envp の代わりにそれを利用する。

_CRTIMP extern char **_environ;

/* ... */

int main(int argc, const char *argv[]) {
   size_t i;
   if (_putenv_s("MY_NEW_VAR", "new_value") != 0) {
     /* エラー処理 */
   }
   if (_environ != NULL) {
      for (i = 0; _environ[i] != NULL; i++) {
         if (puts(_environ[i]) == EOF) {
           /* エラー処理 */
         }
      }
   }
   return 0;
}
適合コード

安全でない envp コードが多数ある場合は、置き換えを利用することで修正にかかる時間を節約することができる。

int main(int argc, char *argv[], char *envp[]) {
  /* ... */
}

上記を以下のように変更する。

#if  defined (_POSIX_) || defined (__USE_POSIX)
  extern char **environ;
  #define envp environ
#else
  _CRTIMP extern char **_environ;
  #define envp _environ
#endif

int main(int argc, char *argv[]) {
  /* ... */
}
リスク評価

環境の変更後に envp 環境ポインタを使用すると、未定義の動作となる可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

ENV31-C

P4

L3

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

 

PRQA QA-C 8.1

0601 (E)

実装済み
関連するガイドライン
CERT C++ Secure Coding Standard ENV31-CPP. Do not rely on an environment pointer following an operation that may invalidate it
参考資料
[ISO/IEC 9899:2011] Section J.5.1, "Environment Arguments"
[MSDN]  getenv, _wgetenv
_environ, _wenviron
_putenv_s, _wputenv_s
[Open Group 2004] setenv()
翻訳元

これは以下のページを翻訳したものです。

ENV31-C. Do not rely on an environment pointer following an operation that may invalidate it (revision 79)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter