- PR -

System.exit() を禁止したい

1
投稿者投稿内容
未記入
大ベテラン
会議室デビュー日: 2003/11/24
投稿数: 121
投稿日時: 2004-06-28 11:32
プラグイン機構を作成しています。特定のインターフェイスを実装したクラスを
プラグインとして呼び出して実行するのですが、呼び出されるプラグイン側で
System.exit() が実行されると、プログラム全体が終了してしまいます。
プラグインとして呼び出されるクラスは URLClassLoader でロードしています。

[質問1] System.exit() を含むプラグインクラスをロードしないようにしたいです。
クラスファイルのバイト列から System.exit() が含まれているか判断することはできますか?
できるとすれば、どのようなバイト列が System.exit() とみなせるのでしょうか。
また、バイト列以外に適切な方法があれば教えてください。

[質問2] System.exit() 以外に、プラグインのような呼び出される子での使用を
禁止すべきコードはありますか?
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2004-06-28 12:42
unibon です。こんにちわ。

引用:

りょうさんの書き込み (2004-06-28 11:32) より:
[質問1] System.exit() を含むプラグインクラスをロードしないようにしたいです。
クラスファイルのバイト列から System.exit() が含まれているか判断することはできますか?
できるとすれば、どのようなバイト列が System.exit() とみなせるのでしょうか。
また、バイト列以外に適切な方法があれば教えてください。


これは分からないですが、バイトコードを直に見るのはおそらく大変すぎてダメだろうと思います。

引用:

りょうさんの書き込み (2004-06-28 11:32) より:
[質問2] System.exit() 以外に、プラグインのような呼び出される子での使用を
禁止すべきコードはありますか?


たとえば、
コード:
int[] a = new int[1000000];
for (int i = 0; i < a.length; i++) {
    a[i] = new int[1000000];
}


も OutOfMemoryError を引き起こすので、たとえ System.exit() を禁止したとしても、プラグインを呼び出す側を保護することはできないと思います。また、上記のコードを禁止することは困難だと思います。
一案ですが、方針を変えて、メソッド呼び出しを禁止するのではなく、署名などで対処されてはどうでしょうか。Java Applet と ActiveX の対立に近いものがありますが。
t-wata
大ベテラン
会議室デビュー日: 2002/07/12
投稿数: 209
お住まい・勤務地: 東京
投稿日時: 2004-06-28 12:42
security managerを使うことで、ソースを一切修正することなく、
プラグインがSystem.exit実行時に、SecurityExceptionがスロー
されるようにできます。以下を参照下さい。
http://java.sun.com/j2se/1.4/ja/docs/ja/guide/security/spec/security-specTOC.fm.html
ただし、設定は結構面倒です。
# aspectjなどバイトコードを変換できるツールを使った方が簡単かもしれない
# けど、禁止したいものが一杯あれば、security managerの方が良い。

Javaには、security managerで使用するpolicyファイルを編集するための
GUIツールもあります。JAVA_HOME/bin/policytoolがそれです。

> クラスファイルのバイト列から System.exit() が含まれているか判断することはできますか?

pluginの全クラスを検査するのでしょうか?jakartaのbcelなどを使えば
できますが、System.exitを禁止するための手段としては適切ではない
ように思います。

> System.exit() 以外に、プラグインのような呼び出される子での使用を
> 禁止すべきコードはありますか?

以下から、「このアクセス権を与えた場合のリスク」というのを見て、
許せるか許せないかを自分のアプリに合わせて判断するしかないです。
http://java.sun.com/j2se/1.4/ja/docs/ja/guide/security/permissions.html
未記入
大ベテラン
会議室デビュー日: 2003/11/24
投稿数: 121
投稿日時: 2004-06-30 08:54
回答ありがとうございます。お返事が遅くなりましたが、
問題が一応解決しましたのでサマリを出します。

・バイト列で判断するのは困難。
・System.exit 以外にも防ぐべきものがある。

上記の理由により、SecurityManager の使用を検討しました。しか
しながら、ポリシーファイルの作成だけで問題を解決することがで
きませんでしたので、今回、私が選択した方法をサマリとして載せ
ておきたいと思います。

ポリシーファイルではコードベース単位でパーミッションを設定で
きるようですが、今回の件では、コードベース単位ではなく、ある
特定のクラスローダーからロードされたクラス、つまりクラスロー
ダー単位、もしくはクラス単位でパーミッションを設定する必要が
ありました。

そこで、自作のクラスローダーを作成して、クラス定義時にパーミッ
ションを設定する方法を行いました。自作クラスローダーを作成し
たのは難しいことをするためではなく、単に protected なメソッド
である ClassLoader#defineClass() を使用するためです。特別難し
いことはありませんでした。

ClassLoader#defineClass() の第5引数に ProtectionDomain があり、
ここにパーミッションの集合を指定すれば良い、ことが分かりまし
た。他の引数、クラス名やバイト列については本題ではないので
触れませんが、JarFile クラス、JarEntry クラスを使用して簡単に
クラスファイルのバイト列を取り出すことができました。

ProtectionDomain は次のコードを使用して、無権限のドメインを作
成しました。

コード:
	CodeSource cs = new CodeSource(null,null); 
	Permissions permissions = new Permissions();
	ProtectionDomain domain = new ProtectionDomain(cs, permissions);



この domain を使用して、ClassLoader#defineClass() を実行して
から ClassLoader#loadClass() でクラスをロードすることにより、
アクセス権を制限したクラスを利用することができる・・・???

なぜかアクセス権が制限されていません。調べてみると、パーミッ
ションを設定するだけでは、アクセス制限は行われず、
SecurityManager を動かしてはじめて、パーミッションが意味を持
つ、ということのようです。そこで、

コード:
	java -Djava.security.manager



を実行して、セキュリティマネージャが起動するようにしました。
すると、アクセス権が機能して、うまくいっている! ように一瞬
見えましたが、実はすべてのコードがアクセス制限されているだけ
でした。

セキュリティマネージャは起動しないといけないけど、プラグイン
以外の通常コードはアクセス権を制限したくない。そこで、ポリ
シーファイルを作成することになりました。policy.txt を次のよう
にフルアクセス許可で作成しました。

コード:
	grant {
		permission java.security.AllPermission;
	};



実行は、次のようにして行いました。

コード:
	java -Djava.security.policy=policy.txt



これで通常のコードは、セキュリティマネージャを起動したまま、
すべてのアクセス権を行使できました。また、自作クラスローダー
を使用してパーミッションを指定したクラスは、正しくアクセス権
が制限されました。
いっきゅう
大ベテラン
会議室デビュー日: 2004/04/04
投稿数: 153
お住まい・勤務地: 兵庫
投稿日時: 2004-06-30 09:52
> 実行は、次のようにして行いました。
>
> コード:
> --------------------------------------------------------------------------------
>
> java -Djava.security.policy=policy.txt
>
> --------------------------------------------------------------------------------
>
>
>
> これで通常のコードは、セキュリティマネージャを起動したまま、
> すべてのアクセス権を行使できました。また、自作クラスローダー
> を使用してパーミッションを指定したクラスは、正しくアクセス権
> が制限されました。

こうすると実行者が意図してセキュリティマネージャを無効にすることも
可能だと思います。
ある程度しかチェックしていないですが
アプリが起動時クラスに
コード:
static {

System.setSecurityManager(
new SecurityManager() {
// アプリ本体のセキュリティを記述
}
}
}


を追加してやればセキュリティマネージャの有効を
アプリにコーティングできると思います。

この後りょうさんの作成したプラグインのクラスローダのセキュリティが
働くかは分かりませんがご参考に、、

結果に興味あるので、うまく動いたらサマリお願いします。

[ メッセージ編集済み 編集者: いっきゅう 編集日時 2004-06-30 10:19 ]
前川
常連さん
会議室デビュー日: 2004/04/27
投稿数: 38
お住まい・勤務地: 1DK
投稿日時: 2004-06-30 13:18
>こうすると実行者が意図してセキュリティマネージャを無効にすることも
>可能だと思います。

その方が嬉しいと思うのですが。
というか、ポリシーファイルを使用するのは「実行者がアプリの権限を制御可能」にする為ですよね。
りょうさんは今回のテストのため手製クラスローダで”無権限”とハードコーディングされましたが、最終的にはこれもユーザが設定ファイルで制御できるようにするんだろうな、と私は考えました。
未記入
大ベテラン
会議室デビュー日: 2003/11/24
投稿数: 121
投稿日時: 2004-07-01 10:08
引用:

こうすると実行者が意図してセキュリティマネージャを無効にする
ことも可能だと思います。


「実行者が意図して」なら問題ないと思いますが、「実行者が意図
せずして」セキュリティが働かなくなってしまうのは好ましくあり
ませんね。引数を忘れてしまったり、jarファイルをダブルクリッ
クで起動したり。そう考えると、やはりプログラムコードでセキュ
リティマネージャを有効にする必要がありそうです。

セキュリティマネージャを起動するために、次のコードが有効だと
考えました。実行者が明示的にセキュリティマネージャを起動した
場合は何もしません。

コード:
    if(System.getSecurityManager() == null) {
        System.setSecurityManager(
            new SecurityManager() {
                // アプリ本体のセキュリティを記述
            }
        );
    }


さて…。SecurityManager のサブクラスを自作しないといけなくなっ
てしまいました。とりあえず、APIリファレンスを見てみると
SecurityManager には checkXXXX というメソッドがたくさんあって、
これらでパーミッションチェックを行っているようです。メソッド
の数がかなり多いので、すべてオーバーライドするのは骨が折れます。
とりあえず、作業にとりかかろうと思い SecurityManager のソース
コードを覗いてみたところ嬉しい発見がありました。checkXXXX と
いうのは簡易呼び出しのために容易されているだけで、内部では
checkPermission() メソッドを呼び出していました。つまり、
checkPermission() だけオーバーライドすれば、なんとかなりそう
です。

コード:
    if(System.getSecurityManager() == null) {
        System.setSecurityManager(
            new SecurityManager() {
                public void checkPermission(Permission perm) {
                    //何もチェックしません。(フルアクセス許可)
                }
            }
        );
    }


これで、セキュリティマネージャを起動したまま、とりあえずアプ
リケーション本体側のフルアクセスを確保できたはずです。実際に
動作確認を行ってみると、たしかにフルアクセスが許可されていま
す。System.setSecurityManager(new SecurityManager()); として
デフォルトのセキュリティマネージャを起動した場合は、無権限と
なりましたので、ちゃんと動いていると言えそうです。

ところが、自作クラスローダーからクラスをロードすると動作がお
かしくなってしまいます。自作クラスローダーでプラグインクラス
をロードしたあとだと、アプリケーション本体のアクセス権限まで
制限されてしまうようで、アプリケーション本体が
AccessControlException を吐き出してしまいました。実行時引数
でセキュリティマネージャを起動した場合はこのような問題は発生
しませんでした。やはり、SecurityManager のサブクラスの実装方
法に問題があるのか、それとも他の部分にも手を加えないといけな
いのか…。手詰まりです。なにかヒントがあったら教えてください。

引用:

りょうさんは今回のテストのため手製クラスローダで”無権限”と
ハードコーディングされましたが、最終的にはこれもユーザが設定
ファイルで制御できるようにするんだろうな、と私は考えました。



この機能も必要ですね。場合によっては、ファイルの読み書きなど
をするプラグインを使用したいということもありますから。さて、
その実装方法を考えてみました。

コード:
    CodeSource cs = new CodeSource(null,null); 
    Permissions permissions = new Permissions();
    ProtectionDomain domain = new ProtectionDomain(cs, permissions);


ここで、パーミッション集合 permissions に必要なパーミッション p
を加えていけば良さそうです。

コード:
    permissions.add(p)


パーミッション p はどのようにして作成しましょうか? 独自の設
定ファイルを作るという方法もありますが、どうせなら標準のポリ
シーファイル形式を読んで、そこからパーミッション定義を取り出
せると楽ができそうですね。

探してみると、公開 API ではありませんが、
sun.security.provider.PolicyParser というものがありました。
これで標準のポリシーファイル形式を読むことができるようです。

コード:
    Reader r = new InputStreamReader(new FileInputStream("plugin.policy.txt"));
    PolicyParser pp = new PolicyParser();
    pp.read(r);
    r.close();
        
    for(Enumeration entries = pp.grantElements(); entries.hasMoreElements(); ) {
        PolicyParser.GrantEntry grant = (PolicyParser.GrantEntry)entries.nextElement();
        for(Enumeration permissions = grant.permissionElements(); permissions.hasMoreElements(); ) {
            PolicyParser.PermissionEntry pe = (PolicyParser.PermissionEntry)permissions.nextElement();
            System.out.println(pe.name + ": " + pe.permission + ", " + pe.action);
        }
    }


うまくいったようです。ポリシーファイルから コードベース、パー
ミッション、アクションを表示することができました。
pe.permisssion は Permission クラスのインスタンスではなく、
文字列表現です。java.security.AllPermission のようなクラス名
になっています。あとは、これらの文字列表現をもとに
Permission インスタンス を組み立てれば良いと思います。この組
み立て部分は、リフレクションを使うことになりますが本題ではあ
りませんので省略します。

なんとかここまで辿り着きました。セキュリティマネージャをプロ
グラムコードで起動できていないというのが心残りではありますが、
とりあえず。
1

スキルアップ/キャリアアップ(JOB@IT)