Bladeren bron

[8.x] [Entitlements] Add checks for native libraries restricted methods (#120775) (#121017)

* [Entitlements] Add checks for native libraries restricted methods (#120775)

* Introducing main21 (does not compile with main23 on the main lib)

* Move foreign API to Java22; fix EntitlementInitialization to work across multiple versions

* [CI] Auto commit changes from spotless

---------

Co-authored-by: elasticsearchmachine <infra-root+elasticsearchmachine@elastic.co>
Lorenzo Dematté 8 maanden geleden
bovenliggende
commit
2171064f5c
17 gewijzigde bestanden met toevoegingen van 513 en 21 verwijderingen
  1. 1 0
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/MrjarPlugin.java
  2. 6 0
      libs/entitlement/bridge/build.gradle
  3. 3 0
      libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java
  4. 12 0
      libs/entitlement/bridge/src/main21/java/org/elasticsearch/entitlement/bridge/Java21EntitlementChecker.java
  5. 27 0
      libs/entitlement/bridge/src/main21/java/org/elasticsearch/entitlement/bridge/Java21EntitlementCheckerHandle.java
  6. 76 0
      libs/entitlement/bridge/src/main22/java/org/elasticsearch/entitlement/bridge/Java22EntitlementChecker.java
  7. 27 0
      libs/entitlement/bridge/src/main22/java/org/elasticsearch/entitlement/bridge/Java22EntitlementCheckerHandle.java
  8. 1 1
      libs/entitlement/bridge/src/main23/java/org/elasticsearch/entitlement/bridge/Java23EntitlementChecker.java
  9. 6 0
      libs/entitlement/build.gradle
  10. 15 1
      libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java
  11. 33 0
      libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/VersionSpecificNativeChecks.java
  12. 118 0
      libs/entitlement/qa/entitlement-test-plugin/src/main22/java/org/elasticsearch/entitlement/qa/test/VersionSpecificNativeChecks.java
  13. 46 17
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java
  14. 12 1
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java
  15. 20 0
      libs/entitlement/src/main21/java/org/elasticsearch/entitlement/runtime/api/Java21ElasticsearchEntitlementChecker.java
  16. 109 0
      libs/entitlement/src/main22/java/org/elasticsearch/entitlement/runtime/api/Java22ElasticsearchEntitlementChecker.java
  17. 1 1
      libs/entitlement/src/main23/java/org/elasticsearch/entitlement/runtime/api/Java23ElasticsearchEntitlementChecker.java

+ 1 - 0
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/MrjarPlugin.java

@@ -154,6 +154,7 @@ public class MrjarPlugin implements Plugin<Project> {
         project.getConfigurations().register("java" + javaVersion);
         TaskProvider<Jar> jarTask = project.getTasks().register("java" + javaVersion + "Jar", Jar.class, task -> {
             task.from(sourceSet.getOutput());
+            task.getArchiveClassifier().set("java" + javaVersion);
         });
         project.getArtifacts().add("java" + javaVersion, jarTask);
     }

+ 6 - 0
libs/entitlement/bridge/build.gradle

@@ -17,6 +17,12 @@ tasks.named('jar').configure {
   if (sourceSets.findByName("main23")) {
     from sourceSets.main23.output
   }
+  if (sourceSets.findByName("main21")) {
+    from sourceSets.main21.output
+  }
+  if (sourceSets.findByName("main22")) {
+    from sourceSets.main22.output
+  }
 }
 
 tasks.withType(CheckForbiddenApisTask).configureEach {

+ 3 - 0
libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

@@ -411,6 +411,7 @@ public interface EntitlementChecker {
     //
     // Load native libraries
     //
+    // Using the list of restricted methods from https://download.java.net/java/early_access/jdk24/docs/api/restricted-list.html
     void check$java_lang_Runtime$load(Class<?> callerClass, Runtime that, String filename);
 
     void check$java_lang_Runtime$loadLibrary(Class<?> callerClass, Runtime that, String libname);
@@ -418,4 +419,6 @@ public interface EntitlementChecker {
     void check$java_lang_System$$load(Class<?> callerClass, String filename);
 
     void check$java_lang_System$$loadLibrary(Class<?> callerClass, String libname);
+
+    void check$java_lang_ModuleLayer$Controller$enableNativeAccess(Class<?> callerClass, ModuleLayer.Controller that, Module target);
 }

+ 12 - 0
libs/entitlement/bridge/src/main21/java/org/elasticsearch/entitlement/bridge/Java21EntitlementChecker.java

@@ -0,0 +1,12 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.bridge;
+
+public interface Java21EntitlementChecker extends EntitlementChecker {}

+ 27 - 0
libs/entitlement/bridge/src/main21/java/org/elasticsearch/entitlement/bridge/Java21EntitlementCheckerHandle.java

@@ -0,0 +1,27 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.bridge;
+
+/**
+ * Java21 variant of {@link EntitlementChecker} handle holder.
+ */
+public class Java21EntitlementCheckerHandle {
+
+    public static Java21EntitlementChecker instance() {
+        return Holder.instance;
+    }
+
+    private static class Holder {
+        private static final Java21EntitlementChecker instance = HandleLoader.load(Java21EntitlementChecker.class);
+    }
+
+    // no construction
+    private Java21EntitlementCheckerHandle() {}
+}

+ 76 - 0
libs/entitlement/bridge/src/main22/java/org/elasticsearch/entitlement/bridge/Java22EntitlementChecker.java

@@ -0,0 +1,76 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.bridge;
+
+import java.lang.foreign.AddressLayout;
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+public interface Java22EntitlementChecker extends Java21EntitlementChecker {
+    // Sealed implementation of java.lang.foreign.AddressLayout
+    void check$jdk_internal_foreign_layout_ValueLayouts$OfAddressImpl$withTargetLayout(
+        Class<?> callerClass,
+        AddressLayout that,
+        MemoryLayout memoryLayout
+    );
+
+    // Sealed implementation of java.lang.foreign.Linker
+    void check$jdk_internal_foreign_abi_AbstractLinker$downcallHandle(
+        Class<?> callerClass,
+        Linker that,
+        FunctionDescriptor function,
+        Linker.Option... options
+    );
+
+    void check$jdk_internal_foreign_abi_AbstractLinker$downcallHandle(
+        Class<?> callerClass,
+        Linker that,
+        MemorySegment address,
+        FunctionDescriptor function,
+        Linker.Option... options
+    );
+
+    void check$jdk_internal_foreign_abi_AbstractLinker$upcallStub(
+        Class<?> callerClass,
+        Linker that,
+        MethodHandle target,
+        FunctionDescriptor function,
+        Arena arena,
+        Linker.Option... options
+    );
+
+    // Sealed implementation for java.lang.foreign.MemorySegment.reinterpret(long)
+    void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(Class<?> callerClass, MemorySegment that, long newSize);
+
+    void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(
+        Class<?> callerClass,
+        MemorySegment that,
+        long newSize,
+        Arena arena,
+        Consumer<MemorySegment> cleanup
+    );
+
+    void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(
+        Class<?> callerClass,
+        MemorySegment that,
+        Arena arena,
+        Consumer<MemorySegment> cleanup
+    );
+
+    void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, String name, Arena arena);
+
+    void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, Path path, Arena arena);
+}

+ 27 - 0
libs/entitlement/bridge/src/main22/java/org/elasticsearch/entitlement/bridge/Java22EntitlementCheckerHandle.java

@@ -0,0 +1,27 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.bridge;
+
+/**
+ * Java22 variant of {@link EntitlementChecker} handle holder.
+ */
+public class Java22EntitlementCheckerHandle {
+
+    public static Java22EntitlementChecker instance() {
+        return Holder.instance;
+    }
+
+    private static class Holder {
+        private static final Java22EntitlementChecker instance = HandleLoader.load(Java22EntitlementChecker.class);
+    }
+
+    // no construction
+    private Java22EntitlementCheckerHandle() {}
+}

+ 1 - 1
libs/entitlement/bridge/src/main23/java/org/elasticsearch/entitlement/bridge/Java23EntitlementChecker.java

@@ -9,4 +9,4 @@
 
 package org.elasticsearch.entitlement.bridge;
 
-public interface Java23EntitlementChecker extends EntitlementChecker {}
+public interface Java23EntitlementChecker extends Java22EntitlementChecker {}

+ 6 - 0
libs/entitlement/build.gradle

@@ -28,6 +28,12 @@ dependencies {
   }
 
   // guarding for intellij
+  if (sourceSets.findByName("main21")) {
+    main21CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java21')
+  }
+  if (sourceSets.findByName("main22")) {
+    main22CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java22')
+  }
   if (sourceSets.findByName("main23")) {
     main23CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java23')
   }

+ 15 - 1
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java

@@ -199,7 +199,21 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
         entry("runtime_load", forPlugins(LoadNativeLibrariesCheckActions::runtimeLoad)),
         entry("runtime_load_library", forPlugins(LoadNativeLibrariesCheckActions::runtimeLoadLibrary)),
         entry("system_load", forPlugins(LoadNativeLibrariesCheckActions::systemLoad)),
-        entry("system_load_library", forPlugins(LoadNativeLibrariesCheckActions::systemLoadLibrary))
+        entry("system_load_library", forPlugins(LoadNativeLibrariesCheckActions::systemLoadLibrary)),
+
+        entry("enable_native_access", new CheckAction(VersionSpecificNativeChecks::enableNativeAccess, false, 22)),
+        entry("address_target_layout", new CheckAction(VersionSpecificNativeChecks::addressLayoutWithTargetLayout, false, 22)),
+        entry("donwncall_handle", new CheckAction(VersionSpecificNativeChecks::linkerDowncallHandle, false, 22)),
+        entry("donwncall_handle_with_address", new CheckAction(VersionSpecificNativeChecks::linkerDowncallHandleWithAddress, false, 22)),
+        entry("upcall_stub", new CheckAction(VersionSpecificNativeChecks::linkerUpcallStub, false, 22)),
+        entry("reinterpret", new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpret, false, 22)),
+        entry("reinterpret_cleanup", new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpretWithCleanup, false, 22)),
+        entry(
+            "reinterpret_size_cleanup",
+            new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpretWithSizeAndCleanup, false, 22)
+        ),
+        entry("symbol_lookup_name", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithName, false, 22)),
+        entry("symbol_lookup_path", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithPath, false, 22))
     )
         .filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())
         .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));

+ 33 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/VersionSpecificNativeChecks.java

@@ -0,0 +1,33 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.qa.test;
+
+class VersionSpecificNativeChecks {
+
+    static void enableNativeAccess() throws Exception {}
+
+    static void addressLayoutWithTargetLayout() {}
+
+    static void linkerDowncallHandle() {}
+
+    static void linkerDowncallHandleWithAddress() {}
+
+    static void linkerUpcallStub() throws NoSuchMethodException {}
+
+    static void memorySegmentReinterpret() {}
+
+    static void memorySegmentReinterpretWithCleanup() {}
+
+    static void memorySegmentReinterpretWithSizeAndCleanup() {}
+
+    static void symbolLookupWithPath() {}
+
+    static void symbolLookupWithName() {}
+}

+ 118 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main22/java/org/elasticsearch/entitlement/qa/test/VersionSpecificNativeChecks.java

@@ -0,0 +1,118 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.qa.test;
+
+import org.elasticsearch.entitlement.qa.entitled.EntitledPlugin;
+
+import java.lang.foreign.AddressLayout;
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleFinder;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
+
+class VersionSpecificNativeChecks {
+
+    static void enableNativeAccess() throws Exception {
+        ModuleLayer parent = ModuleLayer.boot();
+
+        var location = EntitledPlugin.class.getProtectionDomain().getCodeSource().getLocation();
+
+        // We create a layer for our own module, so we have a controller to try and call enableNativeAccess on it.
+        // This works in both the modular and non-modular case: the target module has to be present in the new layer, but its entitlements
+        // and policies do not matter to us: we are checking that the caller is (or isn't) entitled to use enableNativeAccess
+        Configuration cf = parent.configuration()
+            .resolve(ModuleFinder.of(Path.of(location.toURI())), ModuleFinder.of(), Set.of("org.elasticsearch.entitlement.qa.entitled"));
+        var controller = ModuleLayer.defineModulesWithOneLoader(cf, List.of(parent), ClassLoader.getSystemClassLoader());
+        var targetModule = controller.layer().findModule("org.elasticsearch.entitlement.qa.entitled");
+
+        controller.enableNativeAccess(targetModule.get());
+    }
+
+    static void addressLayoutWithTargetLayout() {
+        AddressLayout addressLayout = ADDRESS.withoutTargetLayout();
+        addressLayout.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE));
+    }
+
+    static void linkerDowncallHandle() {
+        Linker linker = Linker.nativeLinker();
+        linker.downcallHandle(FunctionDescriptor.of(JAVA_LONG, ADDRESS));
+    }
+
+    static void linkerDowncallHandleWithAddress() {
+        Linker linker = Linker.nativeLinker();
+        linker.downcallHandle(linker.defaultLookup().find("strlen").get(), FunctionDescriptor.of(JAVA_LONG, ADDRESS));
+    }
+
+    static int callback() {
+        return 0;
+    }
+
+    static void linkerUpcallStub() throws NoSuchMethodException {
+        Linker linker = Linker.nativeLinker();
+
+        MethodHandle mh = null;
+        try {
+            mh = MethodHandles.lookup().findStatic(VersionSpecificNativeChecks.class, "callback", MethodType.methodType(int.class));
+        } catch (IllegalAccessException e) {
+            assert false;
+        }
+
+        FunctionDescriptor callbackDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT);
+        linker.upcallStub(mh, callbackDescriptor, Arena.ofAuto());
+    }
+
+    static void memorySegmentReinterpret() {
+        Arena arena = Arena.ofAuto();
+        MemorySegment segment = arena.allocate(100);
+        segment.reinterpret(50);
+    }
+
+    static void memorySegmentReinterpretWithCleanup() {
+        Arena arena = Arena.ofAuto();
+        MemorySegment segment = arena.allocate(100);
+        segment.reinterpret(Arena.ofAuto(), s -> {});
+    }
+
+    static void memorySegmentReinterpretWithSizeAndCleanup() {
+        Arena arena = Arena.ofAuto();
+        MemorySegment segment = arena.allocate(100);
+        segment.reinterpret(50, Arena.ofAuto(), s -> {});
+    }
+
+    static void symbolLookupWithPath() {
+        try {
+            SymbolLookup.libraryLookup(Path.of("/foo/bar/libFoo.so"), Arena.ofAuto());
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is thrown if path does not point to a valid library (and it does not)
+        }
+    }
+
+    static void symbolLookupWithName() {
+        try {
+            SymbolLookup.libraryLookup("foo", Arena.ofAuto());
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is thrown if path does not point to a valid library (and it does not)
+        }
+    }
+}

+ 46 - 17
libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

@@ -32,6 +32,8 @@ import java.lang.instrument.Instrumentation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -60,11 +62,24 @@ public class EntitlementInitialization {
     public static void initialize(Instrumentation inst) throws Exception {
         manager = initChecker();
 
-        Map<MethodKey, CheckMethod> checkMethods = INSTRUMENTER_FACTORY.lookupMethods(EntitlementChecker.class);
+        Map<MethodKey, CheckMethod> checkMethods = new HashMap<>();
+        int javaVersion = Runtime.version().feature();
+        Set<Class<?>> interfaces = new HashSet<>();
+        for (int i = 17; i <= javaVersion; ++i) {
+            interfaces.add(getVersionSpecificCheckerClass(i, "org.elasticsearch.entitlement.bridge", "EntitlementChecker"));
+        }
+        for (var checkerInterface : interfaces) {
+            checkMethods.putAll(INSTRUMENTER_FACTORY.lookupMethods(checkerInterface));
+        }
 
+        var latestCheckerInterface = getVersionSpecificCheckerClass(
+            javaVersion,
+            "org.elasticsearch.entitlement.bridge",
+            "EntitlementChecker"
+        );
         var classesToTransform = checkMethods.keySet().stream().map(MethodKey::className).collect(Collectors.toSet());
 
-        Instrumenter instrumenter = INSTRUMENTER_FACTORY.newInstrumenter(EntitlementChecker.class, checkMethods);
+        Instrumenter instrumenter = INSTRUMENTER_FACTORY.newInstrumenter(latestCheckerInterface, checkMethods);
         inst.addTransformer(new Transformer(instrumenter, classesToTransform), true);
         inst.retransformClasses(findClassesToRetransform(inst.getAllLoadedClasses(), classesToTransform));
     }
@@ -99,7 +114,9 @@ public class EntitlementInitialization {
                     )
                 ),
                 new Scope("org.apache.httpcomponents.httpclient", List.of(new OutboundNetworkEntitlement())),
-                new Scope("io.netty.transport", List.of(new InboundNetworkEntitlement(), new OutboundNetworkEntitlement()))
+                new Scope("io.netty.transport", List.of(new InboundNetworkEntitlement(), new OutboundNetworkEntitlement())),
+                new Scope("org.apache.lucene.core", List.of(new LoadNativeLibrariesEntitlement())),
+                new Scope("org.elasticsearch.nativeaccess", List.of(new LoadNativeLibrariesEntitlement()))
             )
         );
         // agents run without a module, so this is a special hack for the apm agent
@@ -112,20 +129,11 @@ public class EntitlementInitialization {
     private static ElasticsearchEntitlementChecker initChecker() {
         final PolicyManager policyManager = createPolicyManager();
 
-        int javaVersion = Runtime.version().feature();
-        final String classNamePrefix;
-        if (javaVersion >= 23) {
-            classNamePrefix = "Java23";
-        } else {
-            classNamePrefix = "";
-        }
-        final String className = "org.elasticsearch.entitlement.runtime.api." + classNamePrefix + "ElasticsearchEntitlementChecker";
-        Class<?> clazz;
-        try {
-            clazz = Class.forName(className);
-        } catch (ClassNotFoundException e) {
-            throw new AssertionError("entitlement lib cannot find entitlement impl", e);
-        }
+        Class<?> clazz = getVersionSpecificCheckerClass(
+            Runtime.version().feature(),
+            "org.elasticsearch.entitlement.runtime.api",
+            "ElasticsearchEntitlementChecker"
+        );
         Constructor<?> constructor;
         try {
             constructor = clazz.getConstructor(PolicyManager.class);
@@ -139,6 +147,27 @@ public class EntitlementInitialization {
         }
     }
 
+    private static Class<?> getVersionSpecificCheckerClass(int javaVersion, String packageName, String baseClassName) {
+        final String classNamePrefix;
+        if (javaVersion == 21) {
+            classNamePrefix = "Java21";
+        } else if (javaVersion == 22) {
+            classNamePrefix = "Java22";
+        } else if (javaVersion >= 23) {
+            classNamePrefix = "Java23";
+        } else {
+            classNamePrefix = "";
+        }
+        final String className = packageName + "." + classNamePrefix + baseClassName;
+        Class<?> clazz;
+        try {
+            clazz = Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            throw new AssertionError("entitlement lib cannot find entitlement class " + className, e);
+        }
+        return clazz;
+    }
+
     private static final InstrumentationService INSTRUMENTER_FACTORY = new ProviderLocator<>(
         "entitlement",
         InstrumentationService.class,

+ 12 - 1
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

@@ -62,7 +62,7 @@ import javax.net.ssl.SSLSocketFactory;
  */
 public class ElasticsearchEntitlementChecker implements EntitlementChecker {
 
-    private final PolicyManager policyManager;
+    protected final PolicyManager policyManager;
 
     public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
         this.policyManager = policyManager;
@@ -752,6 +752,7 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
 
     @Override
     public void check$java_lang_Runtime$load(Class<?> callerClass, Runtime that, String filename) {
+        // TODO: check filesystem entitlement READ
         policyManager.checkLoadingNativeLibraries(callerClass);
     }
 
@@ -762,6 +763,7 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
 
     @Override
     public void check$java_lang_System$$load(Class<?> callerClass, String filename) {
+        // TODO: check filesystem entitlement READ
         policyManager.checkLoadingNativeLibraries(callerClass);
     }
 
@@ -769,4 +771,13 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_lang_System$$loadLibrary(Class<?> callerClass, String libname) {
         policyManager.checkLoadingNativeLibraries(callerClass);
     }
+
+    @Override
+    public void check$java_lang_ModuleLayer$Controller$enableNativeAccess(
+        Class<?> callerClass,
+        ModuleLayer.Controller that,
+        Module target
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
 }

+ 20 - 0
libs/entitlement/src/main21/java/org/elasticsearch/entitlement/runtime/api/Java21ElasticsearchEntitlementChecker.java

@@ -0,0 +1,20 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.runtime.api;
+
+import org.elasticsearch.entitlement.bridge.Java21EntitlementChecker;
+import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+
+public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java21EntitlementChecker {
+
+    public Java21ElasticsearchEntitlementChecker(PolicyManager policyManager) {
+        super(policyManager);
+    }
+}

+ 109 - 0
libs/entitlement/src/main22/java/org/elasticsearch/entitlement/runtime/api/Java22ElasticsearchEntitlementChecker.java

@@ -0,0 +1,109 @@
+/*
+ * 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".
+ */
+
+package org.elasticsearch.entitlement.runtime.api;
+
+import org.elasticsearch.entitlement.bridge.Java22EntitlementChecker;
+import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+
+import java.lang.foreign.AddressLayout;
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+public class Java22ElasticsearchEntitlementChecker extends Java21ElasticsearchEntitlementChecker implements Java22EntitlementChecker {
+
+    public Java22ElasticsearchEntitlementChecker(PolicyManager policyManager) {
+        super(policyManager);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_layout_ValueLayouts$OfAddressImpl$withTargetLayout(
+        Class<?> callerClass,
+        AddressLayout that,
+        MemoryLayout memoryLayout
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_abi_AbstractLinker$downcallHandle(
+        Class<?> callerClass,
+        Linker that,
+        FunctionDescriptor function,
+        Linker.Option... options
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_abi_AbstractLinker$downcallHandle(
+        Class<?> callerClass,
+        Linker that,
+        MemorySegment address,
+        FunctionDescriptor function,
+        Linker.Option... options
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_abi_AbstractLinker$upcallStub(
+        Class<?> callerClass,
+        Linker that,
+        MethodHandle target,
+        FunctionDescriptor function,
+        Arena arena,
+        Linker.Option... options
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(Class<?> callerClass, MemorySegment that, long newSize) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(
+        Class<?> callerClass,
+        MemorySegment that,
+        long newSize,
+        Arena arena,
+        Consumer<MemorySegment> cleanup
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(
+        Class<?> callerClass,
+        MemorySegment that,
+        Arena arena,
+        Consumer<MemorySegment> cleanup
+    ) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, String name, Arena arena) {
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+
+    @Override
+    public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, Path path, Arena arena) {
+        // TODO: check filesystem entitlement READ
+        policyManager.checkLoadingNativeLibraries(callerClass);
+    }
+}

+ 1 - 1
libs/entitlement/src/main23/java/org/elasticsearch/entitlement/runtime/api/Java23ElasticsearchEntitlementChecker.java

@@ -12,7 +12,7 @@ package org.elasticsearch.entitlement.runtime.api;
 import org.elasticsearch.entitlement.bridge.Java23EntitlementChecker;
 import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
 
-public class Java23ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java23EntitlementChecker {
+public class Java23ElasticsearchEntitlementChecker extends Java22ElasticsearchEntitlementChecker implements Java23EntitlementChecker {
 
     public Java23ElasticsearchEntitlementChecker(PolicyManager policyManager) {
         super(policyManager);