セキュリティ・マネージャの永続的な無効化
JDK 24以降、セキュリティ・マネージャは永続的に無効化されています。
次に、セキュリティ・マネージャが永続的に無効になった結果としてJDKに加えられた変更のサマリーを示します:
- 起動時にセキュリティ・マネージャを有効にしたり、実行時にカスタム・セキュリティ・マネージャをインストールすることはできません。「セキュリティ・マネージャを有効にするとエラーになる」を参照してください。
- セキュリティ・マネージャAPIの一部のメソッドは、
null
またはfalse
を返すか、コール元のリクエストをパススルーするか、SecurityExceptionまたはUnsupportedOperationExceptionを無条件にスローするように変更されました。「セキュリティ・マネージャAPIの変更」を参照してください。 - 他の多くのAPI、特にSecurityExceptionをスローするAPIが変更されました。「APIに対するその他の変更」を参照してください。
- セキュリティ・マネージャに固有のシステム・プロパティおよびセキュリティ・プロパティは無視されます。「影響を受けるシステムおよびセキュリティ・プロパティ」を参照してください。
- システム・ポリシー・ファイル
$JAVA_HOME/conf/security/java.policy
が削除されました。 - APIの動作、システムまたはセキュリティ・プロパティ、またはJDKファイルへの変更に直接関連しない変更は、「その他の変更点」にリストされています。
セキュリティ・マネージャAPIは、将来のJDKリリースで削除されます。
アプリケーションにセキュリティ・マネージャが必要かどうかを確認し、そこから移行するためのヒントは、「セキュリティ・マネージャからの移行」を参照してください。
セキュリティ・マネージャが永続的に無効になり、将来のリリースで削除される理由の説明などの詳細は、JEP 486: セキュリティ・マネージャの永続的な無効化を参照してください。
セキュリティ・マネージャを有効にするとエラーになる
次のようにして、起動時にセキュリティ・マネージャを有効にしたり、カスタム・セキュリティ・マネージャをインストールすることはできません:
java -Djava.security.manager -jar app.jar
java -Djava.security.manager="" -jar app.jar
java -Djava.security.manager=allow -jar app.jar
java -Djava.security.manager=default -jar app.jar
java -Djava.security.manager=com.example.CustomSecurityManager -jar app.jar
これを試行すると、JVMは次のようにエラーを報告して終了します:
Error occurred during initialization of VM
java.lang.Error: A command line option has attempted to allow or enable the
Security Manager. Enabling a Security Manager is not supported.
at java.lang.System.initPhase3(java.base@24/System.java:2067)
このエラー・メッセージを抑制したり、警告に変更することはできません。
また、System.setSecurityManager(SecurityManager)をコールして、実行時にセキュリティ・マネージャをインストールすることはできません。これを試行すると、JVMは次のメッセージとともにUnsupportedOperationExceptionをスローします:
Setting a Security Manager is not supported
セキュリティ・マネージャAPIの変更
セキュリティ・マネージャAPIは次のもので構成されます:
- java.lang.SecurityManagerクラスのメソッド
- java.securityパッケージのAccessControllerクラス、AccessControlContextクラス、PolicyクラスおよびProtectionDomainクラスのメソッド
- java.lang.SystemクラスのgetSecurityManagerおよびsetSecurityManagerメソッド
セキュリティ・マネージャを永続的に無効にした結果、これらのメソッドは、状況に応じてnull
またはfalse
を返すか、コール元のリクエストをパススルーするか、SecurityExceptionまたはUnsupportedOperationExceptionを無条件にスローします。
セキュリティ・マネージャAPIの削除は、将来のリリースで予定されています。
次の表に、セキュリティ・マネージャAPIに対する変更の詳細を示します:
表1-4 セキュリティ・マネージャの永続的な無効化の影響を受けるAPI
クラス | 影響を受けるAPI |
---|---|
java.lang.SecurityManager |
check*メソッドは、常にSecurityExceptionをスローします。 getSecurityContextメソッドは、権限を付与しないAccessControlContextを返します。特に、checkPermissionメソッドはAccessControlExceptionをスローし、getDomainCombinerメソッドは |
java.lang.System |
setSecurityManagerメソッドは、常にUnsupportedOperationExceptionをスローします。 getSecurityManagerメソッドは、常に |
java.lang.ClassLoader |
defineClass(String, byte[], int, int)メソッドによってクラスに割り当てられるデフォルト・ドメインは以前と同じですが、権限は付与されません。特に、ProtectionDomain.getPermissions()メソッドは常に |
java.security.AccessController |
doPrivilegedメソッド(6つのバリアント)およびdoPrivilegedWithCombinerメソッド(4つのバリアント)は、アクションを即時に実行し、セキュリティ・マネージャが有効になっていない場合と同様に動作します。 checkPermissionメソッドは、常にAccessControlExceptionをスローします。 getContextメソッドは、権限を付与しないAccessControlContextを返します。特に、checkPermissionメソッドはAccessControlExceptionをスローし、getDomainCombinerメソッドはnullを返します。 |
java.security.AccessControlContext |
checkPermissionメソッドは、常にSecurityExceptionをスローします。 |
java.security.Permission |
checkGuardメソッドは、常にSecurityExceptionをスローします。 |
java.security.ProtectionDomain |
現在のポリシーは、常に権限を付与しないPolicyオブジェクトであるため、ProtectionDomainには現在のポリシーからの権限が付与されません。 |
java.security.Policy |
setPolicyメソッドは、常にUnsupportedOperationExceptionをスローします。 getPolicyメソッドは、権限を付与しないPolicyオブジェクトを返します。特に、getParametersメソッドは |
javax.security.auth.Subject |
getSubjectメソッドは、常にUnsupportedOperationExceptionをスローします。 doAsメソッド(2つのバリアント)およびdoAsPrivilegedメソッド(2つのバリアント)は、アクションを起動し、サブジェクトを実行期間にバインドします。 |
java.rmi.RMISecurityManager |
java.lang.SecurityManagerクラスに対するすべての変更は、このサブクラスにも適用されます。 |
java.rmi.server.RMIClassLoader |
getSecurityContextメソッドは、常にnullを返します。 |
APIに対するその他の変更
- 多くのメソッドおよびコンストラクタは、セキュリティ・マネージャが有効で適切な権限が付与されていない場合、SecurityExceptionをスローするように指定されていました。これらのメソッドは、SecurityExceptionをスローしなくなりました。セキュリティ・マネージャが有効になっていない場合と同様に動作します。
- 次のメソッドは何もしません: java.lang.Thread.checkAccess()、java.lang.ThreadGroup.checkAccess()およびjava.util.logging.LogManager.checkAccess()。
影響を受けるシステムおよびセキュリティ・プロパティ
- 次のシステム・プロパティはサポートされていません:
java.security.policy
、jdk.security.filePermCompat
、sun.security.policy.utf8
、sun.security.policy.numcaches
およびsun.net.maxDatagramSockets
。 java.security.debug
システム・プロパティのaccess
およびpolicy
オプションは削除されました。- 次のセキュリティ・プロパティはサポートされていません:
policy.provider
、policy.url.n
、policy.ignoreIdentityScope
、package.access
およびpackage.definition
。
その他の変更点
RMIリモート・コード・ダウンロード・メカニズムが削除されました。java.rmi.server.RMIClassLoaderクラスには、システム・プロパティを介して構成できるサービス・プロバイダ・インタフェース(SPI)があります。デフォルトのSPI実装の詳細は、RMIClassLoader.getDefaultProviderInstance()を参照してください。デフォルト・プロバイダは以前、RMIクライアントがRMIサーバーによって指定されたコードベースからクラスをロードでき、その逆も可能にするリモート・コード・ダウンロードと呼ばれる機能をサポートしていました。リモート・コード・ダウンロードは、セキュリティ・マネージャが有効になっている場合にのみ有効でした。セキュリティ・マネージャが永続的に無効になったため、リモート・コード・ダウンロード・メカニズムは削除されています。回避策として、独自のSPI実装でコードベースからクラス・ロードを実行できます。
セキュリティ・マネージャからの移行
「アプリケーションがセキュリティ・マネージャを有効にしているかどうかを確認する方法」を参照して、アプリケーションがセキュリティ・マネージャを使用しているかどうかを確認してください。
アプリケーションでセキュリティ・マネージャが有効であると判断した場合は、次のようなJDK以外のテクノロジに置き換えることを検討してください:
- コンテナ。コンテナは、軽量で移植可能な仮想化テクノロジの形態であり、アプリケーションとその依存関係をパッケージ化して、異なる環境間で一貫して実行できます。
- ハイパーバイザ。仮想マシン・モニター(VMM)とも呼ばれるハイパーバイザを使用すると、複数のオペレーティング・システムが単一のホスト・コンピュータによって提供されるハードウェア・リソースを共有できます。
- macOSアプリケーション・サンドボックスやLinuxセキュア・コンピューティング(seccomp)機能などのオペレーティング・システム・メカニズム。
セキュリティ・マネージャと同様に、これらのテクノロジは、アプリケーションがローカル・リソースとリモート・リソースを使用する方法を制限できます。たとえば、コードがネットワークにアクセスしてデータを流出させないようにできます。
少数のライブラリが、セキュリティ・マネージャが有効になっている場合にセキュリティ・マネージャを使用するように設計されていました。これらの中には、セキュリティ・マネージャAPIの高度な部分を使用してカスタム実行環境を実装するものもあります。「セキュリティ・マネージャをサポートするライブラリの保守者へのアドバイス」を参照してください。
少数のアプリケーションで、セキュリティ・ポリシーを適用するためではなく、JavaプラットフォームAPIへのコールをインターセプトする手段としてセキュリティ・マネージャを使用しています。アプリケーションでインターセプションが必要な場合、ソース・コードの変更、静的コード分析およびリライト、クラス・ロード時のエージェント・ベースの動的コード・リライトなどの代替手法を使用できます。動的コード・リライトを使用するエージェントの例については、「System.exit(int)のコールからコードをブロックするエージェント」を参照してください。
アプリケーションがセキュリティ・マネージャを有効にしているかどうかを確認する方法
アプリケーションがセキュリティ・マネージャを有効にしているかどうかを判断するには、次の手法を試します:
- スクリプトまたはドキュメントをチェックして、アプリケーションの起動時にセキュリティ・マネージャがコマンドライン・オプションで許可または有効化されているか、またはポリシー・ファイルをインストールして構成する必要があるかを確認します。
- リリース17から23までのJDKでアプリケーションを実行し、セキュリティ・マネージャが非推奨になり、将来のリリースで削除されることを警告するメッセージがコンソールに表示されるかどうかを確認します。JEP 411: 削除のためのセキュリティ・マネージャの非推奨化の警告の発行に関する項を参照してください。
- コマンドライン・オプション
-Djava.security.manager=disallow
を使用して、JDKリリース17から23でアプリケーションを実行します。アプリケーションがSystem.setSecurityManager(SecurityManager)メソッドを使用してカスタム・セキュリティ・マネージャをインストールする場合、JVMはUnsupportedOperationExceptionをスローします。 - リリース17から23までのJDKの
jdeprscan
ツールを使用して、System.setSecurityManager(SecurityManager)やjava.security.Policy.setPolicy(Policy)などの非推奨のセキュリティ・マネージャAPIの使用をスキャンします。
セキュリティ・マネージャをサポートするライブラリの保守者へのアドバイス
少数のライブラリが、セキュリティ・マネージャが有効になっている場合にセキュリティ・マネージャを使用するように設計されていました。これらのライブラリは通常、次の2つの方式を使用します:
-
System.getSecurityManager()をコールして、セキュリティ・マネージャが有効になっているかどうかを確認し、有効になっている場合は、SecurityManager.checkPermissionメソッドのいずれかをコールして、操作を許可するか拒否するかを確認します:
SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(...); }
-
AccessController.doPrivilegedメソッドの1つをコールして、コール元のコードとは異なる権限でコードを実行します:
SomeReturnValue v = AccessController.doPrivileged(() -> { // ... return theResult; });
セキュリティ・マネージャが有効ではないJDK 24以降では、System.getSecurityManager()メソッドおよびAccessController.doPrivilegedメソッドは、以前のJDKリリースでセキュリティ・マネージャが有効になっていない場合と同様に動作します:
- System.getSecurityManager()は
null
を返します - 6つのAccessController.doPrivilegedメソッドは、指定されたアクションを即時に実行します
したがって、これらのメソッドをコールする少数のライブラリは、変更をしなくてもJDK 24以降で動作します。ただし、これらのライブラリの新しいリリースでこれらのメソッドをコールしないことを強くお薦めします。これらのメソッドは、将来のリリースで削除される予定です。
非常に少数のライブラリでは、セキュリティ・マネージャAPIの高度な部分を使用してカスタム実行環境を実装します。たとえば、ライブラリがAccessController.checkPermission(Permission)をコールして独自の権限モデルを適用したり、Policy.setPolicy(Policy)をコールしてカスタム・セキュリティ・マネージャが特定のリソースをアクセス不可として扱うようにすることができます。
- AccessController.checkPermission(Permission)メソッドは、常にAccessControlExceptionをスローします
- Policy.setPolicy(Policy)メソッドは、常にUnsupportedOperationExceptionをスローします
- SecurityManager.check*メソッドは、常にSecurityExceptionをスローします
System.exit(int)のコールからコードをブロックするエージェント
エージェントは、アプリケーションの実行中にアプリケーションのコードを変更できるJavaプログラムです。エージェントは、クラスのロード時にメソッドのバイトコードを変換するか、クラスのロード後にクラスを再定義することで、これを実現します。エージェントの詳細は、java.lang.instrumentパッケージを参照してください。
例1-1は、コードによるSystem.exit(int)のコールをブロックするエージェントです。エージェントは、アプリケーションのmain
メソッドの前にJVMによって実行されるpremain
メソッドを宣言します。このメソッドは、クラス・ファイルをクラス・パスまたはモジュール・パスからロードするときに変換するトランスフォーマを登録します。トランスフォーマは、System.exit(int)へのすべてのコールをthrow new RuntimeException("System.exit not allowed")
に書き換えます。
トランスフォーマは、クラス・ファイルAPIを使用して、クラス・ファイル内のバイトコードを読み書きします。
例1-1 BlockSystemExitAgent.java
import java.lang.classfile.*;
import java.lang.classfile.instruction.InvokeInstruction;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
public class BlockSystemExitAgent {
/*
* Before the application starts, register a transformer of class files.
*/
public static void premain(String agentArgs, Instrumentation inst) {
var transformer = new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classBytes) {
if (loader != null && loader != ClassLoader.getPlatformClassLoader()) {
return blockSystemExit(classBytes);
} else {
return null;
}
}
};
inst.addTransformer(transformer, true);
}
/*
* Rewrite every invokestatic of System.exit(int) to an athrow of RuntimeException.
*/
private static byte[] blockSystemExit(byte[] classBytes) {
var modified = new AtomicBoolean();
ClassFile cf = ClassFile.of(ClassFile.DebugElementsOption.DROP_DEBUG);
ClassModel classModel = cf.parse(classBytes);
Predicate<MethodModel> invokesSystemExit =
methodModel -> methodModel.code()
.map(codeModel ->
codeModel.elementStream()
.anyMatch(BlockSystemExitAgent::isInvocationOfSystemExit))
.orElse(false);
CodeTransform rewriteSystemExit =
(codeBuilder, codeElement) -> {
if (isInvocationOfSystemExit(codeElement)) {
var runtimeException = ClassDesc.of("java.lang.RuntimeException");
codeBuilder.new_(runtimeException)
.dup()
.ldc("System.exit not allowed")
.invokespecial(runtimeException,
"<init>",
MethodTypeDesc.ofDescriptor("(Ljava/lang/String;)V"),
false)
.athrow();
modified.set(true);
} else {
codeBuilder.with(codeElement);
}
};
ClassTransform ct = ClassTransform.transformingMethodBodies(invokesSystemExit, rewriteSystemExit);
byte[] newClassBytes = cf.transformClass(classModel, ct);
if (modified.get()) {
return newClassBytes;
} else {
return null;
}
}
private static boolean isInvocationOfSystemExit(CodeElement codeElement) {
return codeElement instanceof InvokeInstruction i
&& i.opcode() == Opcode.INVOKESTATIC
&& "java/lang/System".equals(i.owner().asInternalName())
&& "exit".equals(i.name().stringValue())
&& "(I)V".equals(i.type().stringValue());
}
}
BlockSystemExitAgent
を使用するには、JARファイルにパッケージ化して、アプリケーションの起動時に-javaagent
オプションで指定する必要があります。
次のステップに従って、BlockSystemExitAgent
をコンパイル、パッケージ化および使用します:
-
ディレクトリ
agentclasses
のBlockSystemExitAgent
をコンパイルします:$ javac -d agentclasses BlockSystemExitAgent.java
-
agent.mf
にJARファイル・マニフェストを作成します:$ cat > agent.mf << EOF Premain-Class: BlockSystemExitAgent Can-Retransform-Classes: true EOF
-
エージェントJARファイルを作成します。
-C agentclasses
の後にピリオドがあることに注意してください:$ jar --create --file=BlockSystemExitAgent.jar --manifest=agent.mf -C agentclasses .
-
エージェントJARファイルを使用してアプリケーション(この例では
app.jar
にパッケージ化されています)を実行します:$ java -javaagent:BlockSystemExitAgent.jar -jar app.jar