JEP 486: Permanently Disable the Security Manager

AuthorSean Mullan & Alex Buckley
OwnerSean Mullan
TypeFeature
ScopeSE
StatusCandidate
Componentsecurity-libs / java.security
Discussionsecurity dash dev at openjdk dot java dot net
EffortL
DurationL
Relates toJEP 411: Deprecate the Security Manager for Removal
Reviewed byAlan Bateman, Mark Reinhold, Stuart Marks
Created2024/08/19 23:43
Updated2024/10/10 17:07
Issue8338625

Summary

The Security Manager has not been the primary means of securing client-side Java code for many years, it has rarely been used to secure server-side code, and it is costly to maintain. We therefore deprecated it for removal in Java 17 via JEP 411 (2021). As the next step toward removing the Security Manager, we will revise the Java Platform specification so that developers cannot enable it and other Platform classes do not refer to it. This change will have no impact on the vast majority of applications, libraries, and tools. We will remove the Security Manager API in a future release.

Goals

Non-Goals

Motivation

The Security Manager has been a feature of the Java Platform since its first release. It is based upon the principle of least privilege: Code is untrusted by default, so it cannot access resources such as the filesystem or the network, and developers place trust in specific code by granting it permission to access specific resources. In theory, this can protect machines and applications against code that contains accidental vulnerabilities or was crafted with malicious intent. In practice, however, the permission scheme is so complex that the Security Manager has always been disabled by default, and its use is exceedingly rare.

Despite the fact that the Security Manager is disabled by default, the least-privilege model induces extraordinary complexity in the Java Platform libraries. From networking, I/O, and JDBC, to XML, AWT, and Swing, the libraries must implement the least-privilege model in case the Security Manager is enabled:

The OpenJDK Core Libraries Group devotes significant time and energy to reviewing every change to any of these methods. Every new API must be designed, and its implementation carefully audited, with the least-privilege model in mind. However, only a tiny number of applications actually enable the Security Manager. To make matters worse, most of them, in our experience, blindly grant all permissions to their code, thereby giving up the benefits of the least-privilege model.

We therefore deprecated the Security Manager, for removal, in Java 17 via JEP 411 (2021). In addition to terminally deprecating the Security Manager API and related APIs, we also revised the JDK to issue warning messages when the Security Manager is enabled. These changes were designed to prepare users and developers for the removal of the Security Manager in a future release.

Deprecating the Security Manager had hardly any impact

JDK 17 and later releases enjoyed wide adoption as developers and enterprises upgraded from JDK 8 and JDK 11. We have seen hardly any discussion in the Java ecosystem about the warnings that these releases issue when enabling the Security Manager. This indicates that the Security Manager is almost completely irrelevant to current Java developers. We appear to have been correct when we said, in JEP 411, that,

"In the quarter-century since the Security Manager was introduced, adoption has been low”,

and,

"In summary, there is no significant interest in developing modern Java applications with the Security Manager."

Since the release of JDK 17, the maintainers of some of the handful of frameworks and tools that supported the Security Manager have removed support for it; these include Derby, Ant, SpotBugs, and Tomcat. The maintainers of Jakarta EE removed the requirement for EE applications to support the Security Manager. We are not aware of any new projects that support the Security Manager.

Moving forward

The vast majority of applications, libraries, and tools do not require the Security Manager, do not recommend the Security Manager, do not use the Security Manager, and do not work if other code uses the Security Manager. It is time for the Java ecosystem to take the next step and stop using the Security Manager entirely.

We will therefore revise the specification of the Security Manager so that developers cannot enable it, and we will revise the specifications of other Java Platform libraries so that they do not delegate resource-access decisions to it. We will leave a minimal version of the java.lang.SecurityManager class in place for compatibility with the few applications, libraries, and tools that still use it. We will remove this class in a future release.

Removing the Security Manager will improve Java security

We believe that the vast majority of Java developers would prefer to see the OpenJDK Core Libraries Group focus on practical security features needed by Internet-facing applications. The above revisions to the specification will allow us to remove the implementation of the Security Manager from the JDK codebase, together with thousands of permission checks and privilege elevations. That, in turn, will make more contributor time and energy available for other work such as

Most contemporary security threats involve malicious data, against which the Security Manager is ill equipped to defend. Removing the implementation of the Security Manager will make more contributor time and energy available for security features that defend against malicious data directly, such as:

Sandboxing Java code

Sandboxing is the ability to restrict code from using local and remote resources. Historically, the Security Manager was responsible for sandboxing applets; we never recommended it for sandboxing entire applications. Java applications should be sandboxed in the same way as native applications, using technologies outside the Java Platform such as containers, hypervisors, and OS mechanisms such as macOS App Sandbox or the Linux seccomp feature. Unlike the Security Manager, these technologies have wide adoption and are relatively easy to learn and use effectively.

A small number of Java applications use their own custom Security Manager to sandbox third party code such as plugins. For example, such a Security Manager might monitor access to the network, to prevent a plugin from exfiltrating data, or it might block calls to System::exit, to prevent a plugin from terminating the application. This interception is possible owing to the 1,000 methods in the JDK which ask the Security Manager for permission before accessing a resource. A Security Manager can inspect the resource and, e.g., log the destination network address before permitting an outbound connection.

The OpenJDK Core Libraries Group is unwilling to maintain the permission checks in all these methods in perpetuity. To intercept resource access by third party code, we recommend deploying an agent. See the Appendix for an example of an agent that blocks System::exit.

The Security Manager in older Java releases

The Security Manager will continue to be available in every release prior to JDK NN. Application deployers who are wary of adopting new releases because they value stability above all else are unlikely ever to upgrade to JDK NN, and therefore will never be affected by changes to the Security Manager in JDK NN or subsequent releases.

Description

In JDK NN, we will:

Enabling the Security Manager in JDK NN is an error

In JDK NN, you cannot enable the Security Manager at startup, nor can you install a custom Security Manager during run time.

How to determine if an application enables the Security Manager

If you are not sure whether your application enables the Security Manager, here are some things you can do to find out:

Rendering the Security Manager API non-functional

The Security Manager API consists of:

We are not removing these methods from Java NN; rather, we are changing them to have no effect. They will, as appropriate, return null or false, or pass through the caller's request, or unconditionally throw a SecurityException. The full set of behavioral changes is available here.

In addition to changing the API itself, we will:

Changes elsewhere in the Java Platform API

Approximately 1,000 constructors and methods in the Platform are specified to throw SecurityException if the Security Manager is enabled and appropriate permissions have not been granted. They span 264 classes, 73 packages, and 25 modules. For example, java.base has 640 methods specified to throw SecurityException.

In Java NN, we will revise the specifications of all such constructors and methods to remove mentions of SecurityException since that exception will now never be thrown. The complete list of revised constructors and methods is available here.

Here is an example of the specification change for a constructor in java.io.FileOutputStream (struck through text is deleted):

public FileOutputStream(String name)
         throws FileNotFoundException

Creates a file output stream to write to the file with the specified
name. A new FileDescriptor object is created to represent this file
connection.

First, if there is a security manager, its checkWrite method is
called with name as its argument.

If the file exists but is a directory rather than a regular file,
does not exist but cannot be created, or cannot be opened for any
other reason then a FileNotFoundException is thrown.

Implementation Requirements:
   Invoking this constructor with the parameter name is equivalent
   to invoking new FileOutputStream(name, false).
Parameters:
   name - the system-dependent file name.
Throws:
   FileNotFoundException - if the file exists but is a directory
   rather than a regular file, does not exist but cannot be created,
   or cannot be opened for any other reason
   SecurityException - if a security manager exists and its checkWrite
   method denies write access to the file.
See Also:
   SecurityManager.checkWrite(java.lang.String)

Advice to maintainers of libraries that support the Security Manager

A small number of libraries were designed to use the Security Manager if it is enabled. These libraries typically employ two idioms:

In JDK NN, where a Security Manager is never enabled, the System::getSecurityManager and AccessController::doPrivileged methods behave as they did in JDK 17 when a Security Manager was not enabled:

Accordingly, the small number of libraries that call these methods will run on JDK NN without change. However, we strongly recommend that new releases of these libraries do not call these methods, which we will remove in a future release.

A very small number of libraries use advanced parts of the Security Manager API to implement a custom execution environment. For example, a library might call AccessController::checkPermission to enforce its own permission model, or call Policy::setPolicy to make custom Security Managers treat certain resources as off-limits. These methods are non-functional in JDK NN in order to provide an execution environment that disallows access to all resources by default. We will remove them in a future JDK release.

Future Work

We are not removing any classes or methods from the Java Platform API in Java NN. In a future release we will remove the Security Manager APIs that we deprecated in Java 17. In future releases we may, further, deprecate and remove additional classes and methods in the java.lang and java.security packages.

Various early Java Platform features were designed around a vision of mobile objects. They used serialization to move code and data between JVMs, and assumed applications would enable the Security Manager to defend against maliciously serialized objects. This vision did not gain any traction. Given the fundamental flaws in serialization and the minimal use of the Security Manager, we have either already removed these features or else we plan to do so:

Separately, the javax.xml APIs allow Java source code to be embedded directly in XSLT and XPath documents as extension functions. This feature is enabled by default but, historically, it was disabled when running with the Security Manager. We will disable this feature by default in a future release, as part of a broader effort toward stricter XML processing.

Testing

The breadth of the Security Manager API and the depth of its support in the JDK codebase is reflected in the approximately 4,000 tests developed for it since JDK 1.0. They fall into three categories:

Permanently disabling the Security Manager will make these tests irrelevant since the functionality will no longer be supported and the concept of a sandbox will no longer exist. Including tests, we will delete over 50,000 lines of code.

Alternatives

Sandboxing

We considered two mechanisms for restricting access to file and network operations:

Each of these replacements, however, depends on hooks in the JDK to enforce the restrictions at the appropriate places, thus retaining a significant fraction of the maintenance burden of the Security Manager. We therefore recommend that developers use other mechanisms, such as containers, to lock down these operations.

Rendering the Security Manager API non-functional

Without a Security Manager, it is preferable for the numerous check* methods in the Security Manager API to always throw an exception so as to avoid unconditionally permitting operations that formerly required a permission check, and thus might not have been permitted. This may be inconvenient for application maintainers, who might have to take some corrective action, but it is better than allowing an application to operate insecurely without notifying the maintainer.

Risks and Assumptions

Appendix

An agent is a Java program that can alter the code of an application while the application is running. Agents achieve this by transforming the bytecodes of methods when classes are loaded, or by redefining classes after they have been loaded.

Here is an agent that blocks code from calling System::exit. The agent declares a premain method that is run by the JVM before the main method of the application. This method registers a transformer that transforms class files as they are loaded from the class path or module path. The transformer rewrites every call to System.exit(int) into throw new RuntimeException("System.exit not allowed").

The transformer reads and writes bytecodes in class files using the Class-File API, which is a preview feature in JDK 23. See the java.lang.classfile package for details. The agent's source code imports the Class-File API and other Java APIs with module import declarations, which are also a preview feature in JDK 23.

import module java.base;
import module java.instrument;

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.transform(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());
    }
}

You must package the agent in a JAR file and specify it via the -javaagent option when starting the application:

# Compile the agent into the agentclasses directory, enabling preview features for JDK 23
$ javac --enable-preview --release 23 -d agentclasses BlockSystemExitAgent.java

# Create JAR file manifest in agent.mf
$ cat > agent.mf << EOF
Premain-Class: BlockSystemExitAgent
Can-Retransform-Classes: true
EOF

# Create the agent JAR (Note there is a period after -C agentclasses)
$ jar --create --file=BlockSystemExitAgent.jar --manifest=agent.mf -C agentclasses .

# Run application with the agent JAR, enabling preview features for JDK 23
$ java --enable-preview -javaagent:BlockSystemExitAgent.jar -jar app.jar