Skip to content

Add package-info.java and javadocs to document Entitlements design and implementation #127023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 22, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

/**
* Contains classes that need to be used directly from instrumented methods.
* It's a minimal shim that is patched into the {@code java.base} module so that it is callable from the class library methods instrumented
* by the agent. The shim retains a {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} instance (inside its
* {@link org.elasticsearch.entitlement.bridge.EntitlementCheckerHandle} holder) and forwards the entitlement checks to the main library,
* that exists in the system classloader.
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} holds all the entitlements check definitions, one for each instrumented
* method.
* <p>
* In order to work across multiple Java versions, this project uses multi-release jars via the {@code mrjar} plugin, which makes it is
* possible to specify classes for specific Java versions in specific {@code src} folders (e.g. {@code main23} for classes available to
* Java 23+).
* All the versioned Java classes are merged into the bridge jar. Therefore, we must prefix the class name
* with the version, e.g. {@code Java23EntitlementCheckerHandle} and {@code Java23EntitlementChecker}.
* </p>
*/
package org.elasticsearch.entitlement.bridge;
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,42 @@ public static EntitlementChecker checker() {
return manager;
}

// Note: referenced by agent reflectively
/**
* Initializes the Entitlement system:
* <ol>
* <li>
* Finds the version-specific subclass of {@link EntitlementChecker} to use
* </li>
* <li>
* Builds the set of methods to instrument using {@link InstrumentationService#lookupMethods}
* </li>
* <li>
* Augment this set “dynamically” using {@link InstrumentationService#lookupImplementationMethod}
* </li>
* <li>
* Creates an {@link Instrumenter} via {@link InstrumentationService#newInstrumenter}, and adds a new {@link Transformer} (derived from
* {@link java.lang.instrument.ClassFileTransformer}) that uses it. Transformers are invoked when a class is about to load, after its
* bytes have been deserialized to memory but before the class is initialized.
* </li>
* <li>
* Re-transforms all already loaded classes: we force the {@link Instrumenter} to run on classes that might have been already loaded
* before entitlement initialization by calling the {@link java.lang.instrument.Instrumentation#retransformClasses} method on all
* classes that were already loaded.
* </li>
* </ol>
* <p>
* The third step is needed as the JDK exposes some API through interfaces that have different (internal) implementations
* depending on the JVM host platform. As we cannot instrument an interfaces, we find its concrete implementation.
* A prime example is {@link FileSystemProvider}, which has different implementations (e.g. {@code UnixFileSystemProvider} or
* {@code WindowsFileSystemProvider}). At runtime, we find the implementation class which is currently used by the JVM, and add
* its methods to the set of methods to instrument. See e.g. {@link EntitlementInitialization#fileSystemProviderChecks}.
* </p>
* <p>
* <strong>NOTE:</strong> this method is referenced by the agent reflectively
* </p>
*
* @param inst the JVM instrumentation class instance
*/
public static void initialize(Instrumentation inst) throws Exception {
manager = initChecker();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,32 @@ record InstrumentationInfo(MethodKey targetMethod, CheckMethod checkMethod) {}

Instrumenter newInstrumenter(Class<?> clazz, Map<MethodKey, CheckMethod> methods);

/**
* This method uses the method names of the provided class to identify the JDK method to instrument; it examines all methods prefixed
* by {@code check$}, and parses the rest of the name to extract the JDK method:
* <ul>
* <li>
* Instance methods have the fully qualified class name (with . replaced by _), followed by $, followed by the method name. Example:
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker#check$java_lang_Runtime$halt}
* </li>
* <li>
* Static methods have the fully qualified class name (with . replaced by _), followed by $$, followed by the method name. Example:
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker#check$java_lang_System$$exit}
* </li>
* <li>
* Constructors have the fully qualified class name (with . replaced by _), followed by $ and nothing else. Example:
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker#check$java_lang_ClassLoader$}
* </li>
* </ul>
* <p>
* <strong>NOTE:</strong> look up of methods using this convention is the primary way we use to identify which methods to instrument,
* but other methods can be added to the map of methods to instrument. See
* {@link org.elasticsearch.entitlement.initialization.EntitlementInitialization#initialize} for details.
* </p>
*
* @param clazz the class to inspect to find methods to instrument
* @throws ClassNotFoundException if the class is not defined or cannot be inspected
*/
Map<MethodKey, CheckMethod> lookupMethods(Class<?> clazz) throws ClassNotFoundException;

InstrumentationInfo lookupImplementationMethod(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,30 @@
package org.elasticsearch.entitlement.instrumentation;

public interface Instrumenter {

/**
* Instruments the appropriate methods of a class by adding a prologue that checks for entitlements.
* The prologue:
* <ol>
* <li>
* gets the {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} instance from the
* {@link org.elasticsearch.entitlement.bridge.EntitlementCheckerHandle} holder;
* </li>
* <li>
* identifies the caller class and pushes it onto the stack;
* </li>
* <li>
* forwards the instrumented function parameters;
* </li>
* <li>
* calls the {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} method corresponding to the method it is injected into
* (e.g. {@code check$java_net_DatagramSocket$receive} for {@link java.net.DatagramSocket#receive}).
* </li>
* </ol>
* @param className the name of the class to instrument
* @param classfileBuffer its bytecode
* @param verify whether we should verify the bytecode before and after instrumentation
* @return the instrumented class bytes
*/
byte[] instrumentClass(String className, byte[] classfileBuffer, boolean verify);
}
Loading