8

JEP draft: Disallow the Dynamic Loading of Agents by Default

 1 year ago
source link: https://openjdk.org/jeps/8306275
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
AuthorsRon Pressler, Alex Buckley
OwnerRon Pressler
TypeFeature
ScopeSE
StatusDraft
Componenthotspot / svc
Created2023/04/18 09:39
Updated2023/04/18 22:32
Issue8306275

Summary

Disallow the dynamic loading of agents into a running JVM by default. Agents are used by profiling tools to instrument Java applications, but agents can also be misused to undermine the integrity of the Java Platform. Following the approach of Integrity and Strong Encapsulation, the application owner is given the final say on whether to allow dynamic loading of agents.

Goals

  • Reassess the balance between serviceability, which involves ad-hoc changes to running code, and integrity, which assumes that running code is not arbitrarily changed.
  • Ensure that a majority of tools (which do not need to load agents dynamically) are unaffected.
  • Align the treatment of dynamically loaded agents with other "superpower" APIs in the Java Platform, such as deep reflection.

Non-Goals

  • It is not a goal to prevent agents from being loaded at JVM startup, when specified by -javaagent or -agentlib on the command line.
  • It is not a goal to change the Attach API that allows a tool to connect to a running JVM for monitoring and management purposes. Thus, tools such as jcmd and jconsole will continue to work without command line options.

Motivation

Agents in the Java Platform

An agent is a piece of code that can alter any code in a running JVM on behalf of a tool. Agents were introduced by the Java Platform Profiling Architecture in Java 5 as a way for tools, notably profilers, to instrument classes. This means altering the code in a class so that it emits events to be consumed by a tool outside the application, without otherwise changing the code's behavior. To achieve this, agents can either transform classes as they are loaded, or redefine classes loaded earlier. Agents can be written in Java using the java.lang.instrument API ("Java agents"), or in native code using the JVM Tool Interface ("JVM TI agents").

Agents were designed with "benign" instrumentation in mind, where the addition of instrumentation does not affect the application's behavior. However, over time, agent developers found use cases, such as Aspect-Oriented Programming, that need to change the application's behavior in arbitrary ways. To ensure that the application owner approved of agents being used by tools, Java 5 required that agents were specified on the command line with -javaagent or -agentlib and loaded them at startup. This represented an explicit granting of privileges by the application, in accordance with the principle of integrity by default.

Serviceability and Dynamically Loaded Agents

Serviceability is the ability of a system operator to monitor, observe, debug and troubleshoot an application while it runs. Java's excellent serviceability has long been a source of pride for the platform.

To support serviceability tools, JDK 6 introduced the non-standard Attach API. This allows an operator with appropriate OS-level privileges to connect a tool to a running JVM and then communicate with it to observe and control its operation. The Attach API is enabled by default, but can be disabled with the command line option -XX:+DisableAttachMechanism. Examples of tools that use the Attach API include:

  • Monitoring and management tools, such as jcmd and jconsole, which observe application metrics and alter configurations. For example, if an application uses the java.util.logging API then an operator can use jconsole to dynamically alter the log level. These tools make use of specialized jcmd protocols, JMX, and the JDK Flight Recorder (JFR).

  • Debugger, which require an agent built into the JVM to be enabled at startup with -agentlib:jdwp. They then communicate with the agent over some IPC channel, but are also able to take advantage of the Attach API.

  • Profilers, and more generally Application Performance Monitoring (APM) tools, use an agent loaded at startup to instrument code so it emits JDK Flight Recorder events for consumption by JDK Mission Control.

Tools can also use the Attach API to load an agent dynamically into a running JVM, i.e., after startup. For example, a profiler can attach to a running program then use the API to load an agent which instruments the program as it runs. As another example, ad-hoc troubleshooting may include the inspection of specific variables at run time without the need to obtain a heap dump that pauses the application; such uses require a dynamically loaded agent that either uses JVM TI to inspect the running program or transforms and instruments loaded classes.

(Note that patching bugs in production application was never a supported use case for dynamically loaded agents, because not only is agents' ability to change loaded classes severely limited, but restarting the application will revert the change and there is no general mechanism to make the dynamic patch persistent.)

Even though the use of dynamically loaded agents gives tools "superpowers" to arbitrarily modify a running program, a tool is directly used by a human operator with appropriate OS credentials. The "human in the loop" is a form of explicit consent to modify the program, and so tools would ideally not be subject to the integrity constraints imposed on the application. Since JDK 6, therefore, the dynamic loading of agents has been allowed by default, but since JDK 9 can be disallowed with the command line option -XX:-EnableDynamicAgentLoading.

Agents and Libraries

Despite a conceptual separation of concerns between libraries and tools, some libraries wish to provide functionality that relies on the code-manipulation capabilities afforded to agents. Such libraries employ a cooperating agent that "leaks" elements of the java.lang.instrument API to the library. Those libraries thus gain "superpowers" that allow them to change any part of the code of a Java application as it runs, in virtually arbitrary ways. To ensure the application owner is "in the loop", some libraries require that their agent be specified on the command line with -javaagent. An example of a library which did this was Quasar, the earliest prototype of what became virtual threads.

Other libraries that desire code-manipulation "superpowers" take a more dubious approach. They want to appear as simple JARs on the class path yet have the instrumentation power given only to agents. They therefore exploit the Attach API to silently inject their agent into the current process behind the application's encapsulation lines. To maintain a semblance of integrity, the Module System in JDK 9 prevented the Attach API from "self attaching", i.e., attaching to the currently running JVM. Unfortunately, that measure has proven insufficient as some libraries have gone to great lengths to circumvent it: They spawn a separate JVM that masquerades as a serviceability tool and dynamically loads the library's agent into the JVM where the library resides. The library thus gains "superpowers" without the application's consent, being able to redefine any class whether in the application (to bypass business-logic invariants) or in the JDK (to alter, e.g., how reflection checks access control). In effect, a library can bypass strong encapsulation, so no invariants enforced by strong encapsulation can be trusted. Integrity is lost.

Toward Integrity by Default

To assure integrity, stronger measures are needed to prevent the misuse of dynamically loaded agents by libraries. Unfortunately, we have not found a simple and automatic way to distinguish between a tool that dynamically loads an agent and a library that dynamically loads an agent. Giving free reign to tools would imply giving free reign to libraries, which is tantamount to giving up on integrity.

Fortunately, most serviceability tools do not rely on dynamically loaded agents. In line with the policy of integrity by default, described in Integrity and Strong Encapsulation, it would be appropriate to demand explicit consent by the application owner for the dynamic loading of agents. In other words, a command line option will be required to enable the dynamic loading of agents. This will prevent libraries from using the Attach API to dynamically load agents and thereby undermine integrity.

A minority of serviceability scenarios will no longer be possible out of the box. Disallowing the dynamic loading of agents by default means that some ad hoc troubleshooting is no longer possible. If the need arises for a dynamically loaded agent, the JVM must be restarted with the command line option enabled. However, most modern server applications are architected for redundancy, and individual nodes can be restarted with the option enabled if needed. Special cases – like a JVM that must never be stopped for maintenance, or a canary process for a new software version which is kept under close observation – can typically be identified in advance.

In summary, requiring explicit consent for the dynamic loading of agents allows the Java ecosystem to attain the vision of integrity without substantially constraining serviceability.

Description

The dynamic loading of agents is disallowed by default. To allow the dynamic loading of agents, users need to specify -XX:+EnableDynamicAgentLoading on the command line.

The disallowed-by-default behavior was proposed in 2017 as part of adding the Module System in JDK 9. The proposal was:

The dynamic loading of JVM TI agents will be disabled by default in a future release. To prepare for that change we recommend that applications that allow dynamic agents start using the option -XX:+EnableDynamicAgentLoading to enable that loading explicitly.

The consensus in 2017 was to defer the proposal from JDK 9 to a later release so that tool maintainers had time to inform their users. This JEP intends to adopt the proposal six years later, in 2023, for JDK 21.

Disallowing the dynamic loading of agents by default affects tools that use the Attach API to load an agent into a JVM some time after the JVM has started, but does not affect tools that use the Attach API for other purposes.

Tools that employ an agent that is loaded at startup are not affected by this change. There is no change to any of the mechanisms that load an agent at JVM startup, either with the -javaagent or -agentlib command line options or with the Launcher-Agent-Class attribute in the main JAR's manifest.

Risks and Assumptions

  • We assume that a majority of serviceability scenarios involve the use of jcmd, jconsole, debuggers, JFR, and APM tools, which do not dynamically load agents and will not be affected.

  • We assume that libraries which perform "superpower" operations by dynamically loading an agent will update their documentation, to ask the application owner either to load their agent at startup with -javaagent or to allow dynamic loading of agents with -XX:+EnableDynamicAgentLoading.

Future Work

  • Advanced profilers that profile native code use JVM TI agents merely to gain access to internal HotSpot mechanisms that can support profiling. When profiling an application in production, they may wish to load the agent dynamically. This use case is best addressed by expanding the capabilities of JFR to perform the task without requiring an agent. JFR is able to cooperate with HotSpot's JIT compiler to capture large batches of stack traces far more efficiently than anything that could be exposed through the JVM TI API or even through the internal, undocumented, AsyncGetCallTrace method that is commonly used by advanced profilers.

  • There may be interesting use cases for code manipulation that could be served by offering an encapsulation-respecting java.lang.instrument.Instrumentation object to libraries directly, with no agent involved. This would allow a library to transform/redefine classes in modules that are open to the library's module.

Alternatives

  • Disallow only the dynamic loading of native JVM TI agents by default, and restrict the capabilities of dynamically loaded Java agents by default (i.e., when the -XX:+EnableDynamicAgentLoading option is not specified) such that they cannot transform/retransform/redefine classes in named modules while they can transform/retransform/redefine classes in unnamed modules. This is more complex and arguably does not support substantially more practical tooling agents. Moreover, it does not preclude a Java agent from employing JNI to grant itself further powers.

  • Disallow only the dynamic loading of native JVM TI agents by default; and declare the instrumentation/redefinition methods of java.lang.instrument.Instrumentation as restricted methods. This will have a similar effect to the first alternative in the sense that it will not allow transforming classes in the unnamed module by default except:

    • This will emit a warning rather than throw an exception for now.
    • Enabling these methods would require an added option --enable-instrumentation=ALL-UNNAMED (since all agents are currently in the unnamed module) as an alternative to doing so with the option -XX:+EnableDynamicLoading.
  • Employ an authentication mechanism that distinguishes between a human-operated tool and a library masquerading as a tool to, by default, allow tools to dynamically load an agent but block libraries from doing so. So far, the ideas we explored were either complex or required a special setup on the command line, which would not have reduced the impact on tools that dynamically load an agent. When a feasible and satisfactory approach is found, we will consider employing it.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK