From 9b33eb50413bd951943658adbd898f5ba37c3eb4 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 10 Sep 2025 10:08:07 -0400 Subject: [PATCH 1/3] Grant manage_threads to java.desktop for Tika --- .../bootstrap/HardcodedEntitlements.java | 8 ++++- .../runtime/policy/PolicyManager.java | 2 +- .../bootstrap/HardcodedEntitlementsTests.java | 33 +++++++++++++++++++ .../bootstrap/TestScopeResolver.java | 12 ++++++- .../runtime/policy/TestPolicyManager.java | 4 +++ 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlementsTests.java diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java index bdc4c92b404aa..826e2d90ea6c8 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java @@ -114,7 +114,13 @@ private static List createServerEntitlements(Path pidFile) { new FilesEntitlement(serverModuleFileDatas) ) ), - new Scope("java.desktop", List.of(new LoadNativeLibrariesEntitlement())), + new Scope( + "java.desktop", + List.of( + new LoadNativeLibrariesEntitlement(), + new ManageThreadsEntitlement() // For sun.java2d.Disposer + ) + ), new Scope( "java.xml", List.of( diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index 3c5948d7824b7..65200b1574e44 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java @@ -94,7 +94,7 @@ public enum ComponentKind { * If this kind corresponds to a single component, this is that component's name; * otherwise null. */ - final String componentName; + public final String componentName; ComponentKind(String componentName) { this.componentName = componentName; diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlementsTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlementsTests.java new file mode 100644 index 0000000000000..e297f64453cfc --- /dev/null +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlementsTests.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.bootstrap; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode; + +import java.io.ByteArrayInputStream; + +import javax.imageio.stream.MemoryCacheImageInputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@WithEntitlementsOnTestCode +public class HardcodedEntitlementsTests extends ESTestCase { + + /** + * The Tika library can do some things we don't ordinarily want to allow. + *

+ * Note that {@link MemoryCacheImageInputStream} doesn't even use {@code Disposer} in JDK 26, + * so it's an open question how much effort this deserves. + */ + public void testTikaPDF() { + new MemoryCacheImageInputStream(new ByteArrayInputStream("test test".getBytes(UTF_8))); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index 3a42485822f3c..0b79209246e25 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -24,6 +24,7 @@ import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.PLUGIN; +import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.SERVER; public record TestScopeResolver(Map scopeMap) { @@ -31,7 +32,16 @@ public record TestScopeResolver(Map scopeMap) PolicyManager.PolicyScope getScope(Class callerClass) { var callerCodeSource = callerClass.getProtectionDomain().getCodeSource(); - assert callerCodeSource != null; + if (callerCodeSource == null) { + // This case happens for JDK modules. Usually those are trivially allowed, but some are excluded, + // and those end up here. + // We have no test build info for those modules, so for now, let's just guess. + if (callerClass.getPackageName().equals("sun.java2d")) { + return new PolicyManager.PolicyScope(SERVER, SERVER.componentName, "java.desktop"); + } else { + throw new IllegalArgumentException("Cannot identify scope for JDK class [" + callerClass + "]"); + } + } var location = callerCodeSource.getLocation().toString(); var scope = scopeMap.get(location); diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index f7397c8f898ed..2be8171c0e4c4 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -97,6 +97,10 @@ public final void clearModuleEntitlementsCache() { @Override protected boolean isTrustedSystemClass(Class requestingClass) { + if (requestingClass.getPackageName().startsWith("sun.java2d")) { + // This is part of the java.desktop module + return false; + } ClassLoader loader = requestingClass.getClassLoader(); return loader == null || loader == ClassLoader.getPlatformClassLoader(); } From 51f3d85d40e1a3a871f19c22b94946a82670ed26 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 10 Sep 2025 16:08:25 -0400 Subject: [PATCH 2/3] Produce a PolicyScope for excluded system modules. Fortunately, the JDK is still modular even if the tests are run sans modules, so we can use the module API to identify the packages in every system module. --- .../runtime/policy/PolicyManager.java | 2 +- .../bootstrap/TestScopeResolver.java | 66 ++++++++++++++----- .../runtime/policy/TestPolicyManager.java | 5 +- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index a65f9d0382ede..85bb220e76b30 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java @@ -54,7 +54,7 @@ public class PolicyManager { */ static final Logger generalLogger = LogManager.getLogger(PolicyManager.class); - static final Set MODULES_EXCLUDED_FROM_SYSTEM_MODULES = Set.of("java.desktop", "java.xml"); + public static final Set MODULES_EXCLUDED_FROM_SYSTEM_MODULES = Set.of("java.desktop", "java.xml"); /** * Identifies a particular entitlement {@link Scope} within a {@link Policy}. diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index 0b79209246e25..91662f3f35773 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -9,11 +9,14 @@ package org.elasticsearch.bootstrap; +import org.elasticsearch.core.Nullable; import org.elasticsearch.core.SuppressForbidden; -import org.elasticsearch.entitlement.runtime.policy.PolicyManager; +import org.elasticsearch.entitlement.runtime.policy.PolicyManager.PolicyScope; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; import java.net.MalformedURLException; import java.net.URL; import java.util.List; @@ -22,25 +25,54 @@ import java.util.TreeMap; import java.util.function.Function; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.PLUGIN; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.SERVER; +import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.MODULES_EXCLUDED_FROM_SYSTEM_MODULES; -public record TestScopeResolver(Map scopeMap) { +public final class TestScopeResolver { private static final Logger logger = LogManager.getLogger(TestScopeResolver.class); + private final Map scopeMap; + private static final Map excludedSystemPackageScopes = computeExcludedSystemPackageScopes(); - PolicyManager.PolicyScope getScope(Class callerClass) { + public TestScopeResolver(Map scopeMap) { + this.scopeMap = scopeMap; + } + + private static Map computeExcludedSystemPackageScopes() { + // Within any one module layer, module names are unique, so we just need the names + Set systemModuleNames = ModuleFinder.ofSystem() + .findAll() + .stream() + .map(ref -> ref.descriptor().name()) + .filter(MODULES_EXCLUDED_FROM_SYSTEM_MODULES::contains) + .collect(toSet()); + + Map result = new TreeMap<>(); + ModuleLayer.boot().modules().stream().filter(m -> systemModuleNames.contains(m.getName())).forEach(m -> { + ModuleDescriptor desc = m.getDescriptor(); + if (desc != null) { + desc.packages().forEach(pkg -> + // Our component identification logic returns SERVER for these + result.put(pkg, new PolicyScope(SERVER, SERVER.componentName, m.getName()))); + } + }); + return result; + } + + public static @Nullable PolicyScope getExcludedSystemPackageScope(Class callerClass) { + return excludedSystemPackageScopes.get(callerClass.getPackageName()); + } + + PolicyScope getScope(Class callerClass) { var callerCodeSource = callerClass.getProtectionDomain().getCodeSource(); if (callerCodeSource == null) { - // This case happens for JDK modules. Usually those are trivially allowed, but some are excluded, - // and those end up here. - // We have no test build info for those modules, so for now, let's just guess. - if (callerClass.getPackageName().equals("sun.java2d")) { - return new PolicyManager.PolicyScope(SERVER, SERVER.componentName, "java.desktop"); - } else { - throw new IllegalArgumentException("Cannot identify scope for JDK class [" + callerClass + "]"); - } + // This only happens for JDK classes. Furthermore, for trivially allowed modules, we shouldn't even get here. + // Hence, this must be an excluded system module, so check for that. + return requireNonNull(getExcludedSystemPackageScope(callerClass)); } var location = callerCodeSource.getLocation().toString(); @@ -48,23 +80,23 @@ PolicyManager.PolicyScope getScope(Class callerClass) { if (scope == null) { // Special cases for libraries not handled by our automatically-generated scopeMap if (callerClass.getPackageName().startsWith("org.bouncycastle")) { - scope = new PolicyManager.PolicyScope(PLUGIN, "security", ALL_UNNAMED); + scope = new PolicyScope(PLUGIN, "security", ALL_UNNAMED); logger.debug("Assuming bouncycastle is part of the security plugin"); } } if (scope == null) { logger.warn("Cannot identify a scope for class [{}], location [{}]", callerClass.getName(), location); - return PolicyManager.PolicyScope.unknown(location); + return PolicyScope.unknown(location); } return scope; } - public static Function, PolicyManager.PolicyScope> createScopeResolver( + public static Function, PolicyScope> createScopeResolver( TestBuildInfo serverBuildInfo, List pluginsBuildInfo, Set modularPlugins ) { - Map scopeMap = new TreeMap<>(); // Sorted to make it easier to read during debugging + Map scopeMap = new TreeMap<>(); // Sorted to make it easier to read during debugging for (var pluginBuildInfo : pluginsBuildInfo) { boolean isModular = modularPlugins.contains(pluginBuildInfo.component()); for (var location : pluginBuildInfo.locations()) { @@ -76,7 +108,7 @@ public static Function, PolicyManager.PolicyScope> createScopeResolver( String module = isModular ? location.module() : ALL_UNNAMED; scopeMap.put( getCodeSource(codeSource, location.representativeClass()), - PolicyManager.PolicyScope.plugin(pluginBuildInfo.component(), module) + PolicyScope.plugin(pluginBuildInfo.component(), module) ); } catch (MalformedURLException e) { throw new IllegalArgumentException("Cannot locate class [" + location.representativeClass() + "]", e); @@ -91,7 +123,7 @@ public static Function, PolicyManager.PolicyScope> createScopeResolver( continue; } try { - scopeMap.put(getCodeSource(classUrl, location.representativeClass()), PolicyManager.PolicyScope.server(location.module())); + scopeMap.put(getCodeSource(classUrl, location.representativeClass()), PolicyScope.server(location.module())); } catch (MalformedURLException e) { throw new IllegalArgumentException("Cannot locate class [" + location.representativeClass() + "]", e); } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index 2be8171c0e4c4..83f85458b2f55 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy; +import org.elasticsearch.bootstrap.TestScopeResolver; import org.elasticsearch.common.util.ArrayUtils; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.test.ESTestCase; @@ -97,8 +98,8 @@ public final void clearModuleEntitlementsCache() { @Override protected boolean isTrustedSystemClass(Class requestingClass) { - if (requestingClass.getPackageName().startsWith("sun.java2d")) { - // This is part of the java.desktop module + if (TestScopeResolver.getExcludedSystemPackageScope(requestingClass) != null) { + // We don't trust the excluded packages even though they are in system modules return false; } ClassLoader loader = requestingClass.getClassLoader(); From 135d74b33ba9f1ce2b0e61fa5fb2a39a3763c688 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 10 Sep 2025 16:14:04 -0400 Subject: [PATCH 3/3] TODO with Jira link --- .../entitlement/bootstrap/HardcodedEntitlements.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java index 826e2d90ea6c8..a1cbf7772ae29 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java @@ -118,7 +118,7 @@ private static List createServerEntitlements(Path pidFile) { "java.desktop", List.of( new LoadNativeLibrariesEntitlement(), - new ManageThreadsEntitlement() // For sun.java2d.Disposer + new ManageThreadsEntitlement() // For sun.java2d.Disposer. TODO: https://elasticco.atlassian.net/browse/ES-12888 ) ), new Scope(