Java はC言語に比べ安全な言語と言われている。 それはJavaがインターネット上で利用されるプログラムを記述すること を前提とし、セキュリティを考慮した設計になっているためである。
Javaのセキュリティ設計の特徴には主に以下のものがある。
ここでは、Javaで通常のプログラミングをする場合に、C言語よりも 安全である点として、配列境界のチェックを比較する。
Javaは配列の範囲外にアクセス(代入、参照)を行なうと必ず例外
(ArrayIndexOutOfBoundsException)を発生する。しかし、Cでは
一切チェックされない。
※ Cでも実行するプログラムが確保しているメモリ領域を超えて
アクセスした場合は、Segmentation Faultなどのエラーが発生する。
しかし、配列の範囲を超えても、そのプログラムのメモリ領域内で
あればエラーは出ない。
以下は、配列cの範囲を超えて値を代入しようとするJavaとCの プログラムである。動作させようとしている内容は全く同じである。
BufTest.java - 配列の範囲を超えて代入するプログラム
public class BufTest {
public static void main(String args[]) {
int a = 0;
int b = 0;
int c[] = new int[3];
c[0] = 100;
c[1] = 200;
c[2] = 300;
c[3] = 400;
c[4] = 500;
c[5] = 600;
c[6] = 700;
c[7] = 800;
System.out.println("c[0] = " + c[0]);
System.out.println("c[1] = " + c[1]);
System.out.println("c[2] = " + c[2]);
System.out.println("b = " + b);
System.out.println("a = " + a);
}
}
BufTest.c - 配列の範囲を超えて代入するプログラム
#include <stdio.h>
int main(void)
{
int a = -1;
int b = -1;
int c[3];
c[0] = 100;
c[1] = 200;
c[2] = 300;
c[3] = 400;
c[4] = 500;
c[5] = 600;
c[6] = 700;
c[7] = 800;
printf("c[0] = %d\n", c[0]);
printf("c[1] = %d\n", c[1]);
printf("c[2] = %d\n", c[2]);
printf("b = %d\n", b);
printf("a = %d\n", a);
}
備考: Cプログラムの実行方法(2021/6/10追記)現在のgccはSSP(stack smashing protection)というプロテクトが デフォルトで有効なため、上記プログラムではバッファオーバーフローが起きない。 このプロテクトを外すには、上記のように -fno-stack-protector オプションを 付けなけらばならない。
Cのプログラムでは、上記の配列や変数と一緒に関数(Javaで言うメソッド) 呼び出しの戻りアドレスもメモリ領域に格納される。このため、 上記の要領で関数の戻りアドレスを書き換えることができる。 戻りアドレスを特定のアドレスに書き換えれば、 特定のアドレスのプログラムを実行させることができる。 管理者権限で実行されているプログラムでこのようなことができるならば、 不正に管理者権限を取得することができるようになる。 このようなセキュリティの弱点を「バッファオーバーフロー脆弱性 (Buffer Overflow Vulnerability)(バッファオーバーランとも言う)」という。近年OSやサーバプログラム 等で見つかっているセキュリティホールの大半はバッファオーバーフロー によるものと言われており深刻な問題となっている。
C言語でバッファオーバーフローを起こさないためには、 必ず境界チェックを行なう必要がある。 境界チェックとは、配列等のデータ領域に値を入力する際に、 確保されているデータ領域外に入力しないようにチェックすることである。 原則として境界チェックは、C言語のプログラマ自身が記述する必要があり、 それをうっかり怠るとセキュリティの脅威となる。Javaでは、通常の コーディングをしている限りは、境界チェックはJava VMが自動的に 行ない、Javaプログラマがコーディングする必要がない。 これはJavaが安全な理由の一つである。
BufTest.java と BufTest.c をそれぞれコンパイル、実行し、結果を 報告せよ。
BufTest.java と BufTest.c の実行結果は異なる。それぞれの実行結果 がなぜそのような結果になるのか説明せよ。
BufTest.c の実行結果から、変数a, b, 配列 c がメモリ上にどのように 配置されているかを推測し示せ。
※ 上記 a,b,cの配置は、OSやコンパイラ、CPUのアーキテクチャに依存する。 つまり、それらが異なると配置方法も異なる。上記 BufTest.c は、 Linux(kernel-2.4)、gcc-2.96(最適化なし)、 x86アーキテクチャ で期待した動作をすることを確認している。