セキュリティ・マネージャの永続的な無効化

JDK 24以降、セキュリティ・マネージャは永続的に無効化されています。

次に、セキュリティ・マネージャが永続的に無効になった結果として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メソッドはnullを返します。

java.lang.System

setSecurityManagerメソッドは、常にUnsupportedOperationExceptionをスローします。

getSecurityManagerメソッドは、常にnullを返します。

java.lang.ClassLoader

defineClass(String, byte[], int, int)メソッドによってクラスに割り当てられるデフォルト・ドメインは以前と同じですが、権限は付与されません。特に、ProtectionDomain.getPermissions()メソッドは常にnullを返します。

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メソッドはnullを返し、getPermissionsメソッドは読取り専用の空のPermissionCollectionを返し、impliesメソッドはfalseを返します。

javax.security.auth.Subject

getSubjectメソッドは、常にUnsupportedOperationExceptionをスローします。

doAsメソッド(2つのバリアント)およびdoAsPrivilegedメソッド(2つのバリアント)は、アクションを起動し、サブジェクトを実行期間にバインドします。

「削除のために非推奨になったSubject.getSubjectメソッドおよびSubject.doAsメソッドからSubject.currentメソッドおよびSubject.callAsメソッドへの移行」を参照してください。

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.policyjdk.security.filePermCompatsun.security.policy.utf8sun.security.policy.numcachesおよびsun.net.maxDatagramSockets
  • java.security.debugシステム・プロパティのaccessおよびpolicyオプションは削除されました。
  • 次のセキュリティ・プロパティはサポートされていません: policy.providerpolicy.url.npolicy.ignoreIdentityScopepackage.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)をコールしてカスタム・セキュリティ・マネージャが特定のリソースをアクセス不可として扱うようにすることができます。

JDK 24以降では、これらのメソッドは常に、すべてのリソースへのアクセスを許可しない実行環境を実装します。その結果、メソッドの動作は以前のJDKリリースと異なります:
  • 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をコンパイル、パッケージ化および使用します:

  1. ディレクトリagentclassesBlockSystemExitAgentをコンパイルします:

    $ javac -d agentclasses BlockSystemExitAgent.java
  2. agent.mfにJARファイル・マニフェストを作成します:

    $ cat > agent.mf << EOF
    Premain-Class: BlockSystemExitAgent
    Can-Retransform-Classes: true
    EOF
  3. エージェントJARファイルを作成します。-C agentclassesの後にピリオドがあることに注意してください:

    $ jar --create --file=BlockSystemExitAgent.jar --manifest=agent.mf -C agentclasses .
  4. エージェントJARファイルを使用してアプリケーション(この例ではapp.jarにパッケージ化されています)を実行します:

    $ java -javaagent:BlockSystemExitAgent.jar -jar app.jar