CON33-C. ライブラリ関数の使用時は競合状態を避ける
C 言語標準ライブラリのいくつかの関数は、スレッドに関しては再入可能であることが保証されていない。ある関数(strtok()
や asctime()
など)は、プロセスごとに関数が割り当てたメモリに格納された結果へのポインタを返し、ある関数(rand()
など)は、プロセスごとに関数が割り当てたメモリに状態情報を格納する。複数のスレッドが同じ関数を呼び出す状況では、同時実行の問題が生じる可能性があり、それにより、多くの場合異常な動作が引き起こされる。さらに、異常終了、サービス運用妨害(DoS)攻撃、データ完全性の侵害など、より深刻な脆弱性につながることもある。
C 言語規格に示されている通り、次のライブラリ関数はデータの競合を避けることが要求されていない。
rand()
getenv()
strtok()
strerror()
asctime()
ctime()
POSIX.1-2008 の「System Interfaces」のセクション 2.9.1 には、スレッドセーフであることが要求されていない関数が他にも示されている。
違反コード (POSIX)
マルチスレッドアプリケーションにおいて、あるシステム関数の呼び出し中にエラーが生じたとする。その場合 strerror()
関数は、エラー番号で指定された、人間が読めるエラー文字列を返す。C 言語規格 [ISO/IEC 9899:2011] のセクション 7.24.6.2 は、strerror()
はデータ競合を避ける必要がないと明確に述べている。規約上は、エラー番号をエラー文字列に割り付ける静的配列を使うことができるため、その配列に他のスレッドからアクセスと変更ができる可能性がある。(このコードは POSIX 固有である。それは、C99 または C11 では、エラーが発生した場合に、fopen()
が errno
を設定することが保証されていないからである)。
errno = 0; FILE* fd = fopen( filename, "r"); if (fd == NULL) { char* errmsg = strerror(errno); printf("Could not open file because of %s\n", errmsg); }
このコードは、最初に errno
を 0 に設定することで、「ERR30-C. 関数を呼び出す前に errno をゼロに初期化し、関数の異常終了時にのみ errno を参照する」に適合させている。
適合コード (POSIX, strerror_r()
)
以下の適合コードでは、POSIX の strerror_r()
関数を使用している。この関数の機能は strerror()
と同じであるが、スレッドの安全性が保証されている。
errno = 0; FILE* fd = fopen( filename, "r"); if (fd == NULL) { char errmsg[BUFSIZ]; if (strerror_r(errno, errmsg, BUFSIZ) != 0) { /* エラー処理 */ } printf("Could not open file because of %s\n", errmsg); }
Linux は strerror_r()
の2つのバージョンを提供している。XSI 準拠のバージョンと GNU 固有のバージョンである。上記の適合コードは、XSI 準拠バージョンを想定している。POSIX で要求される方法で (つまり _POSIX_C_SOURCE
または _XOPEN_SOURCE
を正しく定義して) アプリケーションをコンパイルすると、この XSI 準拠バージョンを使用できる。strerror_r()
の man ページで、使用中のシステム上で利用できるバージョンを確認すること。
適合コード (POSIX, C11, strerror_s()
)
以下の適合コードでは、C 言語規格の附属書 K にある strerror_s()
関数を使用している。この関数の機能は strerror()
と同じであるが、スレッドの安全性が保証されている。また、C 言語規格では、errno
はスレッドに対して局所的な変数であるため、初期化の時点と strerror_s()
が読み取る時点との間での競合状態は発生しない。
errno = 0; FILE* fd = fopen( filename, "r"); if (fd == NULL) { char errmsg[BUFSIZ]; if (strerror_s(errno, errmsg, BUFSIZ) != 0) { /* エラー処理 */ } printf("Could not open file because of %s\n", errmsg); }
附属書 K はオプションであるため、すべての実装で strerror_s()
を利用できるわけではないことに注意する必要がある。
リスク評価
複数のスレッドが同じライブラリ関数を呼び出すことで生じる競合状態は、アプリケーションの異常終了、データ完全性の侵害、サービス運用妨害(DoS)攻撃につながるおそれがある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
CON33-C |
中 |
中 |
高 |
P4 |
L3 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
Compass/ROSE は、このルールの違反を検出できる。 |
関連するガイドライン
CERT C++ Secure Coding Standard | CON03-CPP. Avoid assuming functions are thread safe unless otherwise specified |
参考資料
[Historical information about POSIX.1 Thread Safety] | |
[ISO/IEC 9899:2011] | Section 7.24.6.2, "The strerror Function" |
翻訳元
これは以下のページを翻訳したものです。
CON33-C. Avoid race conditions when using library functions (revision 54)