質問の概要:以下のコードを変更して、信頼できない動的にロードされたコードがセキュリティサンドボックスで実行され、アプリケーションの残りの部分が制限されないようにするにはどうすればよいですか?なぜURLClassLoaderはそれが言うようにそれを処理しないのですか?
編集:AniBに応答するように更新されました。
編集2:更新されたPluginSecurityManagerを追加しました。
私のアプリケーションにはプラグインメカニズムがあり、サードパーティが特定のインターフェイスを実装するクラスを含むJARを提供できます。URLClassLoaderを使用して、そのクラスをロードしてインスタンス化することができます。問題ありません。コードは信頼できない可能性があるため、誤動作を防ぐ必要があります。たとえば、プラグインコードを別のスレッドで実行して、無限ループに入ったり、時間がかかりすぎたりした場合にプラグインコードを強制終了できるようにします。しかし、ネットワーク接続を確立したり、ハードドライブ上のファイルにアクセスしたりできないように、セキュリティサンドボックスを設定しようとすると、私は前向きになります。私の努力は常にプラグインに影響を与えない(アプリケーションと同じ権限を持っている)か、アプリケーションを制限することになります。メインのアプリケーションコードで、必要なことをほぼすべて実行できるようにしたいのですが、
このテーマに関するドキュメントとオンラインリソースは複雑で、混乱を招き、矛盾しています。カスタムSecurityManagerを提供する必要があることをさまざまな場所(この質問など)で読みましたが、JVMがJARのクラスを遅延ロードするため、試してみると問題が発生します。したがって、問題なくインスタンス化できますが、同じJARから別のクラスをインスタンス化するロードされたオブジェクトのメソッドを呼び出すと、JARから読み取る権利が拒否されたために爆発します。
理論的には、SecurityManagerのFilePermissionをチェックして、独自のJARからロードしようとしているかどうかを確認できます。それは問題ありませんが、URLClassLoaderのドキュメントには、「ロードされるクラスには、デフォルトで、URLClassLoaderの作成時に指定されたURLにアクセスするためのアクセス許可のみが付与されます」と記載されています。では、なぜカスタムSecurityManagerが必要なのですか?URLClassLoaderはこれを処理するだけではいけませんか?なんでそうじゃないの?
問題を再現する簡単な例を次に示します。
メインアプリケーション(信頼できる)
PluginTest.java
package test.app;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import test.api.Plugin;
public class PluginTest {
public static void pluginTest(String pathToJar) {
try {
File file = new File(pathToJar);
URL url = file.toURI().toURL();
URLClassLoader cl = new URLClassLoader(new java.net.URL[] { url });
Class<?> clazz = cl.loadClass("test.plugin.MyPlugin");
final Plugin plugin = (Plugin) clazz.newInstance();
PluginThread thread = new PluginThread(new Runnable() {
@Override
public void run() {
plugin.go();
}
});
thread.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Plugin.java
package test.api;
public interface Plugin {
public void go();
}
PluginSecurityManager.java
package test.app;
public class PluginSecurityManager extends SecurityManager {
private boolean _sandboxed;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (!_sandboxed) {
return;
}
// I *could* check FilePermission here, but why doesn't
// URLClassLoader handle it like it says it does?
throw new SecurityException("Permission denied");
}
void enableSandbox() {
_sandboxed = true;
}
void disableSandbox() {
_sandboxed = false;
}
}
PluginThread.java
package test.app;
class PluginThread extends Thread {
PluginThread(Runnable target) {
super(target);
}
@Override
public void run() {
SecurityManager old = System.getSecurityManager();
PluginSecurityManager psm = new PluginSecurityManager();
System.setSecurityManager(psm);
psm.enableSandbox();
super.run();
psm.disableSandbox();
System.setSecurityManager(old);
}
}
プラグインJAR(信頼できない)
MyPlugin.java
package test.plugin;
public MyPlugin implements Plugin {
@Override
public void go() {
new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
doSomethingDangerous(); // permitted without a SecurityManager
}
private void doSomethingDangerous() {
// use your imagination
}
}
更新: プラグインコードが実行される直前に、プラグインセキュリティマネージャーに通知して、使用しているクラスソースを認識できるように変更しました。次に、そのクラスのソースパスの下にあるファイルへのファイルアクセスのみを許可します。これには、アプリケーションの開始時にセキュリティマネージャを一度設定するだけで、プラグインコードを入力および終了するときにセキュリティマネージャを更新できるという優れた利点もあります。
これで問題はほぼ解決しますが、他の質問には答えられません。URLClassLoaderが、言われているようにこれを処理しないのはなぜですか。誰かがその質問に対する答えを持っているかどうかを確認するために、この質問をしばらく開いたままにしておきます。もしそうなら、その人は受け入れられた答えを得るでしょう。それ以外の場合は、URLClassLoaderのドキュメントがあり、カスタムSecurityManagerを作成するための彼のアドバイスが正しいことを前提として、AniBに授与します。
PluginThreadは、クラスファイルへのパスであるPluginSecurityManagerのclassSourceプロパティを設定する必要があります。PluginSecurityManagerは次のようになります。
package test.app;
public class PluginSecurityManager extends SecurityManager {
private String _classSource;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (_classSource == null) {
// Not running plugin code
return;
}
if (perm instanceof FilePermission) {
// Is the request inside the class source?
String path = perm.getName();
boolean inClassSource = path.startsWith(_classSource);
// Is the request for read-only access?
boolean readOnly = "read".equals(perm.getActions());
if (inClassSource && readOnly) {
return;
}
}
throw new SecurityException("Permission denied: " + perm);
}
void setClassSource(String classSource) {
_classSource = classSource;
}
}