ENV30-C. 関数の返り値によって参照されるオブジェクトを変更しない
一部の関数はオブジェクトへのポインタを返すが、このオブジェクトは変更できない。変更すると未定義の動作が引き起こされる。これらの関数には標準の getenv()
、setlocale()
、localeconv()
、および strerror()
関数が含まれる。
C 標準 [ISO/IEC 9899:2011] セクション 7.22.4.6 には getenv
が次のように記載されている。
getenv
関数は、一致する並び(environment list)の要素に結び付けられた文字列へのポインタを返す。このポインタが指す文字列をプログラムで変更してはならないが、引き続くgetenv
関数の呼出しで書き換わることがある。指定された name が見つからないとき、空ポインタを返す。
そのため、getenv()
によって返された文字列を変更する必要がある場合、ローカルコピーを作成する必要がある。getenv()
で返された文字列を変更すると、未定義の動作が引き起こされる。C 標準の附属書 J 「未定義の動作」の 184 も参照すること。
同様に、セクション 7.11.1.1 [ISO/IEC 9899:2011] では、setlocale
および localeconv
を次のように定義している。
setlocale
関数によって返される文字列へのポインタについては、その文字列とそれに関連するカテゴリによってそれ以降の呼び出しが行われるとプログラムのロケールのその部分がリストアされる。このポインタが指す文字列をプログラムで変更してはならないが、引き続くsetlocale
関数の呼出しで書き換わることがある。
localeconv
関数はデータで埋められたオブジェクトへのポインタを返す。返り値のポインタが指す構造体をプログラムで変更してはならないが、引き続くlocaleconv
関数の呼出しで書き換わることがある。
setlocale()
によって返される文字列や localeconv()
によって返される構造体を変更すると、未定義の動作が引き起こされる。附属書 J 「未定義の動作」の 120 と 121 も参照すること。また、C 標準では setlocale()
によって返される文字列の内容に関する要件は規定されていない。そのため、プログラムで文字列の内容や構造に関しての想定は行わない。
最後に、セクション 7.24.6.2 [ISO/IEC 9899:2011] では次のように規定されている。
strerror
関数は文字列へのポインタを返し、その内容はロケール固有のものである。このポインタが指す配列をプログラムで変更してはならないが、引き続くstrerror
関数の呼出しで書き換わることがある。
strerror()
で返された文字列を変更すると、未定義の動作が引き起こされる。附属書 J 「未定義の動作」の 184 も参照すること。
違反コード (getenv()
)
以下のコード例では、getenv()
が返す文字列に対して、二重引用符("
)をすべてアンダースコアに置きかえるという変更を加えている。
void trstr(char *str, char orig, char rep) { while (*str != '\0') { if (*str == orig) { *str = rep; } str++; } } /* ... */ char *env = getenv("TEST_ENV"); if (env == NULL) { /* エラー処理 */ } trstr(env,'"', '_'); /* ... */
適合コード (getenv()
) (ローカルコピー)
上記の違反コードが、環境変数の値をローカルで変更して環境自身は変更しないことを意図していたのであれば、この適合コードのように、環境変数の文字列をローカルにコピーし、それからそのコピーに変更を加える。
const char *env; char *copy_of_env; env = getenv("TEST_ENV"); if (env == NULL) { /* エラー処理 */ } copy_of_env = (char *)malloc(strlen(env) + 1); if (copy_of_env == NULL) { /* エラー処理 */ } strcpy(copy_of_env, env); trstr(copy_of_env,'\"', '_');
適合コード (getenv()
) (POSIX における環境の変更)
環境自身を変更することを意図していたのであれば、この解決法のように POSIX の setenv()
や strdup()
関数を使って変更した文字列を環境に保存する。
const char *env; char *copy_of_env; env = getenv("TEST_ENV"); if (env == NULL) { /* エラー処理 */ } copy_of_env = strdup(env); if (copy_of_env == NULL) { /* エラー処理 */ } trstr(copy_of_env,'\"', '_'); if (setenv("TEST_ENV", copy_of_env, 1) != 0) { /* エラー処理 */ }
違反コード (localeconv()
)
以下のコード例では、C 言語標準ライブラリの関数 localeconv()
から返されるオブジェクトが変更される。
void f2(void) { struct lconv *conv = localeconv(); if ('\0' == conv->decimal_point[0]) { conv->decimal_point = "."; /* 違反 */ } if ('\0' == conv->thousands_sep[0]) { conv->thousands_sep = ","; /* 違反 */ } /* ... */ }
適合コード (localeconv()
) (ローカルコピー)
以下の適合コードでは、オブジェクトをローカルにコピーし、それからそのコピーに変更を加える。
void f2(void) { struct lconv *conv = localeconv(); if (conv == NULL) { /* エラー処理 */ } copy_of_conv = (char *)malloc(sizeof(lconv) + 1); if (copy_of_conv == NULL) { /* エラー処理 */ } memcpy(copy_of_conv, conv, sizeof(lconv)); if ('\0' == copy_of_conv->decimal_point[0]) { copy_of_conv->decimal_point = "."; } if ('\0' == copy_of_conv->thousands_sep[0]) { copy_of_conv->thousands_sep = ","; } /* ... */ }
リスク評価
処理系によっては、これらの関数の返り値が指すオブジェクトを変更すると未定義の動作が引き起こされる。変更が正常に行われても、それ以降の getenv()
、setlocale()
、localeconv()
、または strerror()
関数のコールにより、変更したオブジェクトを上書きされることがある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
ENV30-C |
低 |
中 |
中 |
P4 |
L3 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
このルールの違反を検出できる。特に、 |
関連するガイドライン
CERT C++ Secure Coding Standard | ENV30-CPP. Do not modify the string returned by getenv() |
ISO/IEC TS 17961 (ドラフト) | Modifying the string returned by getenv, localeconv, setlocale, and strerror [libmod] |
参考資料
[ISO/IEC 9899:2011] | Section 7.11.1.1, "The setlocale Function"Section 7.22.4.6, "The getenv Function"Section 7.24.6.2, "The strerror Function" |
[Open Group 2004] | getenv setlocale localeconv |
翻訳元
これは以下のページを翻訳したものです。
ENV30-C. Do not modify the object referenced by the return value of certain functions (revision 82)