MSC14-C. 必要もなくコードをプラットフォーム依存にしない
特定のプラットフォームにおける性能を改善する目的で、プラットフォーム依存のコードを記述することがある。このようなコーディングは(開発時におけるプラットフォーム依存の内容の適切な記録と移植時の適切な対応が行われない場合はとくに)、危険な場合がある。したがって、性能向上などの効果を生まないプラットフォーム依存のコードは避けるべきである。移植時に欠陥を作り込んでしまう可能性がある。
C 標準規格では可搬性のない動作を 4 種類挙げている。C 標準規格の附属書 J の各節には、その具体例が列挙されている。
可搬性のない動作 | 定義 | 附属書 J の節 |
---|---|---|
未規定の動作 | C 標準が二つ以上の可能性を提供し、個々の場合にどの可能性を選択するかに関して何ら要求を課さない動作。 | J.1 |
未定義の動作 | 可搬性がないもしくは正しくないプログラム構成要素を使用したときの動作、または正しくないデータを使用したときの動作であり、C 標準が何ら要求を課さないもの。未定義の動作の例としては、整数演算のオーバーフローに対する動作がある。 | J.2 |
処理系定義の動作 | 未規定の動作のうち、処理系が選択した動作を文書化するもの。 | J.3 |
文化圏固有動作 | 国家、文化および言語の地域規約に依存する動作であり、各処理系がその動作を文書化するもの。 | J.4 |
未定義の動作の一例は、printf
関数において、フォーマット指定子 %s
に対応する引数として、値が null である char*
ポインタを渡す場合である。一部の処理系では (たとえば GNU C Library など) このような場合に対する動作を定めているが、なにも定めていない処理系もある。一部の処理系の動作を期待しているプログラムは異常終了してしまうかもしれない。
未規定の動作の一例は、関数の実引数の評価順序である。
処理系定義の動作の一例は、符号付き整数を右シフトした場合の最上位ビットの伝播法である。
プラットフォームに依存するコードの大半は、別のモジュールに分離することが可能であり、またそうするべきである。そのモジュールは、プラットフォームに関係しない可搬性のあるインターフェイスで、プラットフォーム固有の実装を提供するのである。プラットフォーム固有の詳細に依存せざるをえないプログラムの可搬性を確保するには、標準に基き汎用的で可搬性ある解決策を、プラットフォーム依存コードの代替として常に提供する必要がある。そうすることで、新たなプラットフォームへの移植は格段に容易になり、また、プログラムが想定している条件が新しい環境で通用しない場合に引き起こされるセキュリティ上のリスクを回避することができる。
違反コード
以下の違反コード例は、符号なし整数のオーバーフローの判定に補数演算子を使用している。
unsigned int ui; unsigned int ui2; unsigned int sum; if (~ui < ui2) { /* エラー処理 */ } sum = ui + ui2;
このコードは処理系が2の補数表現を使用することを想定して書かれている。この想定は多くの場合正しいが、言語規格が保証するものではない。
またこのコードは「INT14-C. 同じデータに対してビット単位の演算と算術演算を行わない」にも違反している。
適合コード
以下の適合コードでは、符号なし整数のオーバーフローの判定を、規格に厳密に合致した方法で行っている。
unsigned int ui; unsigned int ui2; unsigned int sum; if (UINT_MAX - ui < ui2) { /* エラー処理 */ } sum = ui + ui2;
違反コードで紹介した判定方法の方が高速になるようであれば、コンパイラベンダに相談した方がよい。これらの判定が等価であるなら、最適化が行われるべきだからだ。性能が同じであるなら、可搬性のある方法を使用するほうがよい。
違反コード (strerror_r
)
GNU libc が実装する strerror_r 関数は char*
を返すように宣言されているが、この動作は POSIX 仕様と競合する。以下の違反コード例では、戻り値を fprintf
の書式指定 %s
に対する引数として渡しており、戻り値の型に依存している。POSIX に準拠して strerror_r()
の戻り値の型を int
と宣言しているプラットフォームでは、この動作は未定義になるだろう。
void f() { char buf[BUFSIZ]; fprintf(stderr, "Error: %s\n", strerror_r(errno, buf, sizeof buf)); }
適合コード (strerror_r
)
以下の適合コードでは、<string.h>
ヘッダをインクルードする前に POSIX への適合を示すマクロを宣言することで POSIX 準拠でない strerror_r
の宣言を無効にし、関数のエラー発生時にはバッファに文字列 "Unknown error"
をコピーするという処理を行っている。また、C 標準規格の附属書Kに規定されている strerror_s()
関数を使用する方法もある。
strerror_r()
の呼出し結果は int
型変数に代入されていることに注意。この代入は、char*
を返す strerror_r()
を誤って呼び出す場合に対応した多層防御策である。規格に合致したコンパイラであれば、char*
から int
へという不正な変換に対して診断メッセージを出すことが求められる。
#define _XOPEN_SOURCE 600 #include <string.h> #include <stdio.h> #include <errno.h> void f() { char buf[BUFSIZ]; int result; result = strerror_r(errno, buf, sizeof buf); if (0 != result) { strcpy(buf, "Unknown error"); } fprintf(stderr, "Error: %s\n", buf); }
リスク評価
必要もなくコードをプラットフォーム依存にすることは、文字通り不要である。プラットフォーム依存なコードを避けることで、プラットフォームに関する想定が無効になることで引き起こされる移植性に関するエラーを取り除くことができる。
レコメンデーション | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
MSC14-C | 低 | 低 | 中 | P2 | L3 |
自動検出(最新の情報はこちら)
ツール | バージョン | チェッカー | 説明 |
---|---|---|---|
PRQA QA-C | v8.2 |
0202,284,581,634,1434,0240,0241,0246,0551,0601, 0633,0635,0660,0662,0830,0831,0899,1001,1002, 1003,1006,1008,1012,1014,1015,1019,1020,1021, 1022,1026,1028,1029,1034,1035,1036,1037,1038, 1041,1042,1043,1044,1045,1046,3664 |
部分的に実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | MSC14-CPP. Do not introduce unnecessary platform dependencies |
ISO/IEC TR 24772 | Unspecified Behaviour [BQF] |
参考資料
[Dowd 2006] | Chapter 6, "C Language Issues" ("Arithmetic Boundary Conditions," pp. 211–223) |
[Seacord 2013] | Chapter 5, "Integer Security" |
翻訳元
これは以下のページを翻訳したものです。
MSC14-C. Do not introduce unnecessary platform dependencies (revision 46)