単純であることは、安全をもたらします。セキュリティのメンテナンスを覚え ておくことよりも、何かを忘れることの方が簡単です。 安全を多重化する ---------------- 入力値の確認は、クライアントがサーバリソースを多く使うことを防ぐのに役 立ちます。それが役立つ場所でのみ、制限を加えてください。例えば、簡単な "行の最大長"は、ほとんど全てのクライアントの入力の長さを制限することに なるでしょう。 入力値の確認を信頼しないで下さい。多分、どこかで失敗することになります。 多分、考えてもいなかった場所で、誰かが関数を呼ぶことになります。多分、 誰かは何らかの理由で、入力値の確認を制限しないでしょう。ポイントは、入 力値が想像していたものでなくても、セキュリティホールになりえない、とい うことです。 メモリを信用してはいけません。ソースコードのどこかでバッファが溢れたら、 それを乗っ取ることを簡単にしないことです。例えば、以下のソースコードが あったとします: static char staticbuf[100]; .. char stackbuf[100]; strcpy(stackbuf, staticbuf); staticbuf が [100] で宣言されているからと言って、それはそれ以上のデータ が含まれていないとは意味していません。静的バッファのオーバーフローは、 動的な乗っ取ることはできませんが、 strcpy() での stackbuf のオーバーフ ローはそれが可能になります。常に範囲のチェックをしてデータのコピーをし て下さい。 バッファオーバーフローの防止 ---------------------------- 直接バッファを書き換えることは避けています。書き換えるときは、全てバッファ オーバーフローに対しての保護の保証のある、バッファー API (lib/buffer.h) を通しています。同様に、文字列を安全に扱う色々な API (lib/str.h, lib/strfuncs.h) があります。Dovecot は同様に配列を安全に扱う API (lib/array.h) を持っています。 もし、バッファを直接書き換えるとき、明らかに安全でないならば、ソースコー ドに /* @UNSAFE */ と記述します。明らかに安全なソースコードは、 (buffer, sizeof(buffer)) の引数を持っています。たとえバッファサイズが 計算できたとしても、安全ではないと記述します。 バッファに対する const は必要なときにいつでも使うことができます。それは 間違って上書きできないことが保証されます。 "char *" は NULL で終わっている文字列にのみ使います。"unsigned char *" は、 NULL で終わっていると保証できません。 free() を避ける --------------- 解放されたメモリのアクセスは、 C コードで解決しなければならない最も難しい 問題です。本当に解決するのは、ガーベージコレクターを使うことだけですが、 コードを書く方法を根本的に変更することなしで、 portable GC を書くことは可 能ではありません。 以前、 Boehm GC のサポートを追加しましたが、現在それはうまく動作している ようではありません。いずれにしても、それを必要としたくありません。 ほとんどの free() 呼び出しを避けるいくつかの方法があります:データスタッ クとメモリプールです。 データスタックは C のコントロールスタックといくつかの点で似たような動作を します。alloca() は非常にそれに近いものですが、大きな違いが1つあります: スタックフレームは明示的に定義されるので、関数はデータスタックから割り当 てられた値を返すことができます。t_strdup_printf() の呼び出しはこれが役立 つことを示した素晴らしい例です。任意のサイズバッファを作成して、値を切り 詰める snprintf() を使うことなく、バッファサイズが十分に大きいかを気にす ることなく、 t_strdup_printf() を使うことができます。 データスタックの割り当てを小さくすることを試すので、データスタックは、デー タスタックが使う最高のメモリサイズが、プロセスの残りの時間、保存されます。 データスタックサイズの初期値は 32kB で、この値は通常つかう上では十分な値 です。 lib/data-stack.h を参考にしてみてください。 複数の断片からオブジェクトを作成する必要がある場合、メモリプールは便利で あり、全てを一度に free することができます。Dovecot の Memory Pool APIは、 単なるメモリ割り当てのための抽象クラスにすぎません。calloc() と realloc() と free() でのメモリ割り当てを行うための system_pool があり、データスタッ クからメモリの割り当てのためにプールを作成することができます。関数が複数 のオブジェクトで使用するメモリの割り当てが必要ならば、メモリがどこに割り 当てられているのか指定する呼び出し元を許可するパラメータのような struct プールが必要になるでしょう。 lib/mempool.h を参考にしてみてください。 安全な終了化 ------------ ポインタを free するときは必ず、ポインタに NULL を設定します。そうすると、 偶然に再度 free したときに、セキュリティホールの原因にならなくなります。 Dovecot はほとんどの free() 呼び出しで、自動的にこれを行っていますが、 全ての _destroy() 関数が、NULL を設定したポインタのポインタの変数を取る 習慣を付けなければなりません。 秘密を保持しない ---------------- バッファオーバーフローによる読み込みアクセスに対して、特別な保護はしていな いため、一切の機密はメモリに保存しません。ユーザとの機密情報の保護は、複数 のプロセスを使用することにより対応しています。 パスワードなどを扱ったとき、必要なくなった後にはメモリから削除します。そ の際、 memset() はコンパイラで最適化されますので、 safe_memset() を使って います。 GCC の拡張の使用 ---------------- GCC はいくらかの潜在的なエラーを発見することが簡単です: フォーマット文字列処理の脆弱は、全ての関数で、フォーマット文字列に __attr_format__() と __attr_format_arg__() を記述し、GCC の -Wformat=2 オプションを使用することで、防ぐことができます。 -W オプションは signed と unsigned の値を比較しないようにチェックします。 GCC が将来、存在的な integer の切り詰めがあるときは必ず、警告を発するよ うになることを望みます。-Wconversion オプションは一部行いますが、その本 来の意味とは違いますし、多くのその他の使えない警告を出します。 union を安全に使う ------------------ 以下のコードがあったとします: union { unsigned int number; char *str; } u; もし、ユーザが任意の数字を設定して、 union を文字列としてアクセスすることが 可能であるならば、任意のメモリの場所を読んだり書いたりすることが可能になり ます。 これを扱うには、2つの方法があります。1つ目は、 union を全く使わずに struct を使うことです。union 保存のための余分な数バイトのメモリが実際に必要 なくなります。 もう1つの方法は、正しく union にアクセスするマクロだけを通して、 union に アクセスすることです。lib-imap/imap-parser.h ファイルの IMAP_ARG_*() マクロ を参考にして下さい。