prev教育サーバーのページ

Javaの安全性

Java はC言語に比べ安全な言語と言われている。 それはJavaがインターネット上で利用されるプログラムを記述すること を前提とし、セキュリティを考慮した設計になっているためである。

Javaのセキュリティ設計の特徴には主に以下のものがある。

  1. Javaバイトコードの正当性チェック
    JavaVMがJavaバイトコード(.class, .jarファイル)を読む時に、 不正なコードが含まれていないかチェックする。
  2. Java Sandbox モデル
    JavaVM がJavaプログラムがアクセスすべき領域の範囲外を超えて アクセスしていないかチェックされている。 また、Javaバイトコードごとに、信頼されるクラスかどうかを区別し、 信頼されていないクラスをロードしメソッドを実行しようとすると 例外(SecurityException)を発生する。
    しかし、Javaアプリケーションは無制限に信頼され、Javaアプレット ではネットワークからロードされるクラスは全て信頼されないという 単純な仕様になっていた。このため、Javaアプリケーションでは、何 でもできるか、Javaアプレットは制限だらけでできることが非常に限 られていた。例えば、アプレットでローカルファイルの読み書きが できなかった。
  3. Javaコード署名モデル
    Java Sandboxモデルではアプレットの制約があまりにも大きすぎた。 そこでJDK1.1から、ネットワークを通じて読み込まれるJavaコードに 電子署名し、出所の明示、改竄されていないことを保証する機構が 加わった。また、アプレットの制約も緩くすることができ、例えば、 正しく署名されているクラスであれば、ファイルの読み書きなど 今までのアプレットでは許可されなかった機能が使えるようになった。

配列境界のチェック - Java と C の比較

ここでは、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プログラムの実行方法
Linuxの場合: Windowsの場合:

(2021/6/10追記)現在のgccはSSP(stack smashing protection)というプロテクトが デフォルトで有効なため、上記プログラムではバッファオーバーフローが起きない。 このプロテクトを外すには、上記のように -fno-stack-protector オプションを 付けなけらばならない。

Cのプログラムでは、上記の配列や変数と一緒に関数(Javaで言うメソッド) 呼び出しの戻りアドレスもメモリ領域に格納される。このため、 上記の要領で関数の戻りアドレスを書き換えることができる。 戻りアドレスを特定のアドレスに書き換えれば、 特定のアドレスのプログラムを実行させることができる。 管理者権限で実行されているプログラムでこのようなことができるならば、 不正に管理者権限を取得することができるようになる。 このようなセキュリティの弱点を「バッファオーバーフロー脆弱性 (Buffer Overflow Vulnerability)(バッファオーバーランとも言う)」という。近年OSやサーバプログラム 等で見つかっているセキュリティホールの大半はバッファオーバーフロー によるものと言われており深刻な問題となっている。

C言語でバッファオーバーフローを起こさないためには、 必ず境界チェックを行なう必要がある。 境界チェックとは、配列等のデータ領域に値を入力する際に、 確保されているデータ領域外に入力しないようにチェックすることである。 原則として境界チェックは、C言語のプログラマ自身が記述する必要があり、 それをうっかり怠るとセキュリティの脅威となる。Javaでは、通常の コーディングをしている限りは、境界チェックはJava VMが自動的に 行ない、Javaプログラマがコーディングする必要がない。 これはJavaが安全な理由の一つである。

演習1-1

BufTest.java と BufTest.c をそれぞれコンパイル、実行し、結果を 報告せよ。

演習1-2

BufTest.java と BufTest.c の実行結果は異なる。それぞれの実行結果 がなぜそのような結果になるのか説明せよ。

演習1-3

BufTest.c の実行結果から、変数a, b, 配列 c がメモリ上にどのように 配置されているかを推測し示せ。

※ 上記 a,b,cの配置は、OSやコンパイラ、CPUのアーキテクチャに依存する。 つまり、それらが異なると配置方法も異なる。上記 BufTest.c は、 Linux(kernel-2.4)、gcc-2.96(最適化なし)、 x86アーキテクチャ で期待した動作をすることを確認している。


ohmi@rsch.tuis.ac.jp (2004年4月11日〜2021年6月10日)

Valid HTML 4.01!