JEP 451: Prepare to Disallow the Dynamic Loading of Agents

AuthorRon Pressler & Alex Buckley
OwnerRon Pressler
TypeFeature
ScopeSE
StatusClosed / Delivered
Release21
Componenthotspot / svc
Discussionjigsaw dash dev at openjdk dot org, serviceability dash dev at openjdk dot org
Reviewed byAlan Bateman, Dan Heidinga
Endorsed byMark Reinhold, Serguei Spitsyn
Created2023/04/18 09:39
Updated2023/08/21 14:44
Issue8306275

Summary

Issue warnings when agents are loaded dynamically into a running JVM. These warnings aim to prepare users for a future release which disallows the dynamic loading of agents by default in order to improve integrity by default. Serviceability tools that load agents at startup will not cause warnings to be issued in any release.

Goals

Non-Goals

Motivation

Agents in the Java Platform

An agent is a component that can alter the code of an application while the application is running. Agents were introduced by the Java Platform Profiling Architecture in JDK 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. Agents achieve this either by transforming classes during class loading, or by redefining classes loaded earlier. They can be written in Java code 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 application behavior. However, advanced developers found use cases such as Aspect-Oriented Programming that change application behavior in arbitrary ways. There is also nothing to stop an agent from altering code outside the application, such as code in the JDK itself. To ensure that the owner of an application approved the use of agents, JDK 5 required agents to be specified on the command line with the -javaagent or -agentlib options, and loaded the agents immediately at startup. This represented an explicit grant of privileges by the application owner.

Serviceability and dynamically loaded agents

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

To support serviceability tools, JDK 6 introduced the Attach API. The Attach API is not part of the Java Platform but, rather, a JDK API supported for external use. It allows a tool launched with appropriate operating-system privileges to connect to a running JVM, either local or remote, and communicate with that JVM to observe and control its operation. The Attach API is enabled by default but can be disabled with the -XX:+DisableAttachMechanism option on the command line.

Examples of tools that use the Attach API include:

The Attach API also allows a tool to load an agent dynamically, into a running JVM. This capability supports advanced use cases that involve altering arbitrary code on the fly. Examples of tools that load agents dynamically include:

(Very advanced developers sometimes fix bugs in production environments by writing an agent that patches buggy code and loading that agent dynamically. However, this is not a supported use case and has never been recommended. An agent's ability to redefine loaded classes is subject to limitations, so the ability to fix bugs by patching is limited. Moreover, an agent cannot persist the changes that it makes, so restarting the application will revert the change.)

Dynamically loaded agents give serviceability tools the superpower to alter a running application. However, attaching a tool is triggered by a human operator with appropriate operating-system credentials. This human in the loop grants approval to alter the application, so serviceability tools are not subject to the integrity constraints imposed upon other code. Therefore the dynamic loading of agents has been allowed by default, though in JDK 9 and above it can be disallowed with the -XX:-EnableDynamicAgentLoading option on the command line.

Agents and libraries

Despite a conceptual separation of concerns between libraries and tools, some libraries provide functionality that relies upon the code-altering superpower afforded to agents. For example, a mocking library might redefine application classes to bypass business-logic invariants, while a white-box testing library might redefine JDK classes so that reflection over private fields is always permitted. To obtain these capabilities, a library can employ an agent that obtains an all-powerful Instrumentation object from the JVM and conveys it to the library.

Some such libraries ensure that the application owner grants approval to alter the application by requiring the library's agent to be specified on the command line with the -javaagent option. An example of a library that did this was Quasar, an early prototype of what later became Virtual Threads (JEP 444).

Other libraries take a more dubious approach, obtaining capabilities without the approval of the application owner. They use the Attach API to silently connect to the JVMs in which they run and load agents dynamically, in effect masquerading as serviceability tools. To maintain integrity, JDK 9 and later releases prevent code from connecting to the current JVM by default. (Such connections can be enabled via -Djdk.attach.allowAttachSelf=true.) However, that measure has proven insufficient: Some libraries now spawn a second JVM which connects to the first and loads the agent there, alongside the library.

If a library uses agents to silently redefine JDK classes, thereby bypassing strong encapsulation, then none of the invariants enforced by strong encapsulation can be trusted. Integrity is lost.

Toward integrity by default

To assure integrity, we need stronger measures to prevent the misuse by libraries of dynamically loaded agents. Unfortunately, we have not found a simple and automatic way to distinguish between a serviceability 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 by default.

We therefore propose to require the dynamic loading of agents to be approved by the application owner — just as we have required the startup-time loading of agents to be approved by the application owner since JDK 5. This change will move the Java Platform closer to the long-term vision of integrity by default. In practical terms, the application owner will have to choose to allow the dynamic loading of agents via a command line option.

Fortunately, most serviceability tools do not rely upon dynamically loaded agents. However, disallowing the dynamic loading of agents by default means that ad-hoc troubleshooting techniques that require dynamically loading an agent will no longer work out-of-the-box. If the need arises to dynamically load an agent then the JVM must be restarted with the appropriate command-line option in order to grant the application owner's approval.

The impact of this change will be mitigated by the fact that most modern server applications are designed with redundancy, so individual nodes can be restarted with the command line option as needed. Special cases — such as 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 so that the dynamic loading of agents can be enabled from the start.

Requiring application owners to grant approval for dynamically loaded agents will allow the Java ecosystem to attain the vision of integrity by default without substantially constraining serviceability.

Description

In JDK 21, the dynamic loading of agents is allowed but the JVM issues a warning when it occurs. For example:

WARNING: A {Java,JVM TI} agent has been loaded dynamically (file:/u/bob/agent.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release

To allow tools to dynamically load agents without warnings, users must run with the -XX:+EnableDynamicAgentLoading option on the command line.

Running with -Djdk.instrument.traceUsage causes the methods of the java.lang.instrument API to print a message and a stack trace when used. This helps identify libraries that incorrectly use dynamically loaded agents instead of agents loaded at startup. Maintainers of libraries that load agents dynamically are encouraged to update their documentation to describe how users can load agents at startup; the various deployment options are given by the java.lang.instrument API.

In some future release, the dynamic loading of agents will be disallowed by default. Out of the box, any use of the Attach API to dynamically load an agent will cause an exception to be thrown:

com.sun.tools.attach.AgentLoadException: Failed to load agent library: \
Dynamic agent loading is not enabled. Use -XX:+EnableDynamicAgentLoading \
to launch target VM.

To allow the dynamic loading of agents when it is disallowed by default, users must run with -XX:+EnableDynamicAgentLoading on the command line.

To prepare for the changed default in the future release, users of JDK 9 or any later release can explicitly disallow the dynamic loading of agents by running with -XX:-EnableDynamicAgentLoading on the command line.

Tools that employ agents loaded at startup are unaffected by these changes. The meaning and operation of the -javaagent option, the -agentlib option, and the Launcher-Agent-Class JAR-file attribute are unchanged.

Tools that use the Attach API for purposes other than dynamically loading an agent are unaffected by these changes.

Libraries must not load agents dynamically. Libraries employing an agent must load it at startup with the -javaagent/-agentlib options.

Historical note

Disallowing the dynamic loading of agents by default was originally proposed in 2017 as part of adding modules to the platform 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 change from JDK 9 to a later release so that tool maintainers would have time to inform their users. Nevertheless, when we have strengthened encapsulation in the past we have issued warnings in a preceding release in order to build awareness of the upcoming change. This JEP follows the same procedure.

Risks and Assumptions

Future Work

Alternatives