9.1. C と C++

C と C++ プログラムに存在する最大のセキュリティ上の問題の 1 つは、バッファ オーバーフローです。詳しくは Chapter 5 を見てください。 C はさらに例外をサポートしていないという弱点を持っていて、重大なエラーを 無視して、安直にプログラムをコーディングできます。

C や C++ には他にも問題があります。それは、開発者が自分でメモリ管理をしな ければならない点です(たとえば、malloc()や alloc()、free()、new、free)。 メモリ管理に失敗すると、結果としてセキュリティ上の弱点になるかもしれません。 もっと深刻な問題は、プログラムが開放してはいけないメモリを間違って開放できる 点です(つまり、C++ で malloc() と new 命令を混ぜて使うと、不正な命令を利用 していることになります)。 こうなると、場合によっては GNU/Linux システムのように、すぐクラッシュする ケースもあります。またあるケースでは攻撃者がそこにつけ込んで、勝手にコード を実行させてしまうかもしれません。 たとえば、2001 年 3 月 11 日に zlib ライブラリでこの問題が発生していると アナウンスされ、それを使っている多くのプログラムが影響を受けました。 したがって、GNU/Linux 上でプログラムをテストする時には、MALLOC_CHECK_ 環境 変数に 1 もしくは 2 を設定すべきです。そして、自分のプログラムを実行するの に当たっては、0、1、2 いずれかに設定するのを検討してもよいと思います。 この設定をする理由については、GNU/Linux の malloc(3) に説明があります。

最近のバージョンの Linux libc(5.4.23 以降)と GNU libc(2.x)では、 malloc の動作が、環境変数によって調整できる実装になっています。 MALLOC_CHECK_ が設定されていると、専用の(非効率な)実装が用いられて、 単純なエラーには耐え られるようになります。単純なエラーとは、free()を同じ引き数で 2 度呼び出して しまったり、 1 バイトだけ余計に取ったり(オフ・バイ・ワンのバグ)する等です。 しかし、これらのエラーすべてを防げるわけではなく、その場合にはメモリリーク が発生します。 MALLOC_CHECK_ を 0 に設定すると、ヒープの破壊に対して 警告を出さずに、そのままにしておきます。1 に設定すると、診断メッセージが 標準エラー出力に表示されます。 2 に設定すると、ただちに abort() が呼び 出されます。 プロセスが実際にクラッシュするのがずっと後になり、クラッシュした時点で 本当の原因を探し出すのが非常に困難な場合には、これが役立ちます。

未使用のメモリを開放しないと(たとえば、free()を使って)、未使用のメモリ がたまってしまいます。未使用のメモリがたまり過ぎると、プログラムが動作 停止してしまうかもしれません。 未使用のメモリが攻撃者に利用され、サービス拒否を起こす結果になる可能性も あります。 理屈上では、攻撃者がメモリをフラグメント化して、サービス拒否を起こせます。 しかし普通これはかなり非現実的で、攻撃としては危険性が低くなります。

型宣言をする時にはできるだけ厳密にしてください。 利用できるなら「enum」を使って、列挙型の値を定義してください(特別な値を 持った「char」や「int」を使うのではなく)。 enum は特に switch 文の値で役に立ちます。コンパイラが、正しい値を適用している かどうかを判定してくれます。 値が負にならなければ、「unsigned」を使用するのが適切です。

C や C++ でやっかいなのは、文字型である「char」が signed もしくは unsigned どちらにもなる点です(コンパイラやマシンによって違います)。 signed char にハイビットを設定し、整数として保存すると負になります。 これが脆弱性になるケースがあります。 一般的には、char や signed char のかわりに「unsigned char」を使って、 バッファやポインタに利用してください。そして、127(0x7f)以上の値になるかも しれない文字データを扱うなら、キャストしてください。

C と C++ における型チェックのサポートは明らかにいい加減です。しかし、少なく ともチェックのレベルを上げれば、間違いのいくつかは自動的に検知できます。 コンパイラの警告をできるだけ有効にしてコードを修正し、警告が何も出ないように コンパイルしてください。必ず ANSI のプロトタイプ宣言を独立したヘッダファイル (.h)に入れて利用し、関数呼び出しすべてが必ず正確な型になっているようにして ください。 gcc を使って C や C++ をコンパイルするなら、コンパイル時のフラグとして、少なく とも下記を設定してください(たくさんの警告メッセージが有効になります)。また、 警告はすべて取り除くようにしてください(警告には、データフロー分析を行って、 高レベルの最適化を図った時にだけ検知されるものがあります。その場合、-O2 が 使われていることを覚えておいてください)。
gcc -Wall -Wpointer-arith -Wstrict-prototypes -O2
「-W -pedantic」としてもよいと思います。

C や C++ コンパイラが、不正な書式文字列を検出できるケースが多々あります。 たとえば gcc では、__attribute__()機能(C 拡張機能の 1 つ)を使って、不正な 書式文字列への警告が可能になり、該当する関数に印をつけます。また、この機能 を使ったとしても、コードの互換性は無くなりません。 ここでは、ヘッダファイル(.h)に何を入れるのかの例を挙げておきます。
 /* in header.h */
 #ifndef __GNUC__
 #  define __attribute__(x) /*nothing*/
 #endif

 extern void logprintf(const char *format, ...)
    __attribute__((format(printf,1,2)));
 extern void logprintva(const char *format, va_list args)
    __attribute__((format(printf,1,0)));
「format」属性は、「printf」と「scanf」で利用できます。その後に続く数字は 書式文字列のパラメタ数と最初の可変引数パラメタ(個々に)です。 この点については、GNU のドキュメントで解りやすく解説しています。 他の機能として、__attribute__ には「noreturn」や「const」等があります。

C や C++ の開発者がよく起こすエラーを無くしましょう。 たとえば、「==」を使うつもりで「=」を使わないよう、注意してください。