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]
main
やwmain
に渡される環境ブロックは、現在の環境の"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)