From 24179d56721ffe6fe692fe2e0e722d4ff3be827c Mon Sep 17 00:00:00 2001 From: Patrick Doyle <810052+prdoyle@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:21:55 -0400 Subject: [PATCH] Grant manage_threads to java.desktop for Tika (#134454) * Grant manage_threads to java.desktop for Tika * 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. * TODO with Jira link --- .../bootstrap/HardcodedEntitlements.java | 8 ++- .../runtime/policy/PolicyManager.java | 4 +- .../bootstrap/HardcodedEntitlementsTests.java | 33 ++++++++++ .../bootstrap/TestScopeResolver.java | 62 ++++++++++++++++--- .../runtime/policy/TestPolicyManager.java | 5 ++ 5 files changed, 99 insertions(+), 13 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..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 @@ -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. TODO: https://elasticco.atlassian.net/browse/ES-12888 + ) + ), 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..0a02be6bcbe68 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}. @@ -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..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,39 +25,78 @@ 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(); - assert callerCodeSource != null; + if (callerCodeSource == null) { + // 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(); var scope = scopeMap.get(location); 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()) { @@ -66,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); @@ -81,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 f7397c8f898ed..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,6 +98,10 @@ public final void clearModuleEntitlementsCache() { @Override protected boolean isTrustedSystemClass(Class requestingClass) { + 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(); return loader == null || loader == ClassLoader.getPlatformClassLoader(); }